1 /*
2 * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25 package javax.swing.text;
26
27 import java.lang.reflect.Method;
28
29 import java.security.AccessController;
30 import java.security.PrivilegedAction;
31
32 import java.beans.Transient;
33 import java.util.Collections;
34 import java.util.HashMap;
35 import java.util.Hashtable;
36 import java.util.Enumeration;
37 import java.util.Vector;
38 import java.util.Map;
39
40 import java.util.concurrent.*;
41
42 import java.io.*;
43
44 import java.awt.*;
45 import java.awt.event.*;
46 import java.awt.print.*;
47 import java.awt.datatransfer.*;
48 import java.awt.im.InputContext;
49 import java.awt.im.InputMethodRequests;
50 import java.awt.font.TextHitInfo;
51 import java.awt.font.TextAttribute;
52
53 import java.awt.print.Printable;
54 import java.awt.print.PrinterException;
55
56 import javax.print.PrintService;
57 import javax.print.attribute.PrintRequestAttributeSet;
58
59 import java.text.*;
60 import java.text.AttributedCharacterIterator.Attribute;
61
62 import javax.swing.*;
63 import javax.swing.event.*;
64 import javax.swing.plaf.*;
65
66 import javax.accessibility.*;
67
68 import javax.print.attribute.*;
69
70 import sun.awt.AppContext;
71
72
73 import sun.swing.PrintingStatus;
74 import sun.swing.SwingUtilities2;
75 import sun.swing.text.TextComponentPrintable;
76 import sun.swing.SwingAccessor;
77
78 /**
79 * <code>JTextComponent</code> is the base class for swing text
80 * components. It tries to be compatible with the
81 * <code>java.awt.TextComponent</code> class
82 * where it can reasonably do so. Also provided are other services
83 * for additional flexibility (beyond the pluggable UI and bean
84 * support).
85 * You can find information on how to use the functionality
86 * this class provides in
87 * <a href="http://java.sun.com/docs/books/tutorial/uiswing/components/generaltext.html">General Rules for Using Text Components</a>,
88 * a section in <em>The Java Tutorial.</em>
89 *
90 * <p>
91 * <dl>
92 * <dt><b><font size=+1>Caret Changes</font></b>
93 * <dd>
94 * The caret is a pluggable object in swing text components.
95 * Notification of changes to the caret position and the selection
96 * are sent to implementations of the <code>CaretListener</code>
97 * interface that have been registered with the text component.
98 * The UI will install a default caret unless a customized caret
99 * has been set. <br>
100 * By default the caret tracks all the document changes
101 * performed on the Event Dispatching Thread and updates it's position
102 * accordingly if an insertion occurs before or at the caret position
103 * or a removal occurs before the caret position. <code>DefaultCaret</code>
104 * tries to make itself visible which may lead to scrolling
105 * of a text component within <code>JScrollPane</code>. The default caret
106 * behavior can be changed by the {@link DefaultCaret#setUpdatePolicy} method.
107 * <br>
108 * <b>Note</b>: Non-editable text components also have a caret though
109 * it may not be painted.
110 *
111 * <p>
112 * <dt><b><font size=+1>Commands</font></b>
113 * <dd>
114 * Text components provide a number of commands that can be used
115 * to manipulate the component. This is essentially the way that
116 * the component expresses its capabilities. These are expressed
117 * in terms of the swing <code>Action</code> interface,
118 * using the <code>TextAction</code> implementation.
119 * The set of commands supported by the text component can be
120 * found with the {@link #getActions} method. These actions
121 * can be bound to key events, fired from buttons, etc.
122 *
123 * <p>
124 * <dt><b><font size=+1>Text Input</font></b>
125 * <dd>
126 * The text components support flexible and internationalized text input, using
127 * keymaps and the input method framework, while maintaining compatibility with
128 * the AWT listener model.
129 * <p>
130 * A {@link javax.swing.text.Keymap} lets an application bind key
131 * strokes to actions.
132 * In order to allow keymaps to be shared across multiple text components, they
133 * can use actions that extend <code>TextAction</code>.
134 * <code>TextAction</code> can determine which <code>JTextComponent</code>
135 * most recently has or had focus and therefore is the subject of
136 * the action (In the case that the <code>ActionEvent</code>
137 * sent to the action doesn't contain the target text component as its source).
138 * <p>
139 * The <a href="../../../../technotes/guides/imf/spec.html">input method framework</a>
140 * lets text components interact with input methods, separate software
141 * components that preprocess events to let users enter thousands of
142 * different characters using keyboards with far fewer keys.
143 * <code>JTextComponent</code> is an <em>active client</em> of
144 * the framework, so it implements the preferred user interface for interacting
145 * with input methods. As a consequence, some key events do not reach the text
146 * component because they are handled by an input method, and some text input
147 * reaches the text component as committed text within an {@link
148 * java.awt.event.InputMethodEvent} instead of as a key event.
149 * The complete text input is the combination of the characters in
150 * <code>keyTyped</code> key events and committed text in input method events.
151 * <p>
152 * The AWT listener model lets applications attach event listeners to
153 * components in order to bind events to actions. Swing encourages the
154 * use of keymaps instead of listeners, but maintains compatibility
155 * with listeners by giving the listeners a chance to steal an event
156 * by consuming it.
157 * <p>
158 * Keyboard event and input method events are handled in the following stages,
159 * with each stage capable of consuming the event:
160 *
161 * <table border=1 summary="Stages of keyboard and input method event handling">
162 * <tr>
163 * <th id="stage"><p align="left">Stage</p></th>
164 * <th id="ke"><p align="left">KeyEvent</p></th>
165 * <th id="ime"><p align="left">InputMethodEvent</p></th></tr>
166 * <tr><td headers="stage">1. </td>
167 * <td headers="ke">input methods </td>
168 * <td headers="ime">(generated here)</td></tr>
169 * <tr><td headers="stage">2. </td>
170 * <td headers="ke">focus manager </td>
171 * <td headers="ime"></td>
172 * </tr>
173 * <tr>
174 * <td headers="stage">3. </td>
175 * <td headers="ke">registered key listeners</td>
176 * <td headers="ime">registered input method listeners</tr>
177 * <tr>
178 * <td headers="stage">4. </td>
179 * <td headers="ke"></td>
180 * <td headers="ime">input method handling in JTextComponent</tr>
181 * <tr>
182 * <td headers="stage">5. </td><td headers="ke ime" colspan=2>keymap handling using the current keymap</td></tr>
183 * <tr><td headers="stage">6. </td><td headers="ke">keyboard handling in JComponent (e.g. accelerators, component navigation, etc.)</td>
184 * <td headers="ime"></td></tr>
185 * </table>
186 *
187 * <p>
188 * To maintain compatibility with applications that listen to key
189 * events but are not aware of input method events, the input
190 * method handling in stage 4 provides a compatibility mode for
191 * components that do not process input method events. For these
192 * components, the committed text is converted to keyTyped key events
193 * and processed in the key event pipeline starting at stage 3
194 * instead of in the input method event pipeline.
195 * <p>
196 * By default the component will create a keymap (named <b>DEFAULT_KEYMAP</b>)
197 * that is shared by all JTextComponent instances as the default keymap.
198 * Typically a look-and-feel implementation will install a different keymap
199 * that resolves to the default keymap for those bindings not found in the
200 * different keymap. The minimal bindings include:
201 * <ul>
202 * <li>inserting content into the editor for the
203 * printable keys.
204 * <li>removing content with the backspace and del
205 * keys.
206 * <li>caret movement forward and backward
207 * </ul>
208 *
209 * <p>
210 * <dt><b><font size=+1>Model/View Split</font></b>
211 * <dd>
212 * The text components have a model-view split. A text component pulls
213 * together the objects used to represent the model, view, and controller.
214 * The text document model may be shared by other views which act as observers
215 * of the model (e.g. a document may be shared by multiple components).
216 *
217 * <p align=center><img src="doc-files/editor.gif" alt="Diagram showing interaction between Controller, Document, events, and ViewFactory"
218 * HEIGHT=358 WIDTH=587></p>
219 *
220 * <p>
221 * The model is defined by the {@link Document} interface.
222 * This is intended to provide a flexible text storage mechanism
223 * that tracks change during edits and can be extended to more sophisticated
224 * models. The model interfaces are meant to capture the capabilities of
225 * expression given by SGML, a system used to express a wide variety of
226 * content.
227 * Each modification to the document causes notification of the
228 * details of the change to be sent to all observers in the form of a
229 * {@link DocumentEvent} which allows the views to stay up to date with the model.
230 * This event is sent to observers that have implemented the
231 * {@link DocumentListener}
232 * interface and registered interest with the model being observed.
233 *
234 * <p>
235 * <dt><b><font size=+1>Location Information</font></b>
236 * <dd>
237 * The capability of determining the location of text in
238 * the view is provided. There are two methods, {@link #modelToView}
239 * and {@link #viewToModel} for determining this information.
240 *
241 * <p>
242 * <dt><b><font size=+1>Undo/Redo support</font></b>
243 * <dd>
244 * Support for an edit history mechanism is provided to allow
245 * undo/redo operations. The text component does not itself
246 * provide the history buffer by default, but does provide
247 * the <code>UndoableEdit</code> records that can be used in conjunction
248 * with a history buffer to provide the undo/redo support.
249 * The support is provided by the Document model, which allows
250 * one to attach UndoableEditListener implementations.
251 *
252 * <p>
253 * <dt><b><font size=+1>Thread Safety</font></b>
254 * <dd>
255 * The swing text components provide some support of thread
256 * safe operations. Because of the high level of configurability
257 * of the text components, it is possible to circumvent the
258 * protection provided. The protection primarily comes from
259 * the model, so the documentation of <code>AbstractDocument</code>
260 * describes the assumptions of the protection provided.
261 * The methods that are safe to call asynchronously are marked
262 * with comments.
263 *
264 * <p>
265 * <dt><b><font size=+1>Newlines</font></b>
266 * <dd>
267 * For a discussion on how newlines are handled, see
268 * <a href="DefaultEditorKit.html">DefaultEditorKit</a>.
269 *
270 * <p>
271 * <dt><b><font size=+1>Printing support</font></b>
272 * <dd>
273 * Several {@link #print print} methods are provided for basic
274 * document printing. If more advanced printing is needed, use the
275 * {@link #getPrintable} method.
276 * </dl>
277 *
278 * <p>
279 * <strong>Warning:</strong>
280 * Serialized objects of this class will not be compatible with
281 * future Swing releases. The current serialization support is
282 * appropriate for short term storage or RMI between applications running
283 * the same version of Swing. As of 1.4, support for long term storage
284 * of all JavaBeans<sup><font size="-2">TM</font></sup>
285 * has been added to the <code>java.beans</code> package.
286 * Please see {@link java.beans.XMLEncoder}.
287 *
288 * @beaninfo
289 * attribute: isContainer false
290 *
291 * @author Timothy Prinzing
292 * @author Igor Kushnirskiy (printing support)
293 * @see Document
294 * @see DocumentEvent
295 * @see DocumentListener
296 * @see Caret
297 * @see CaretEvent
298 * @see CaretListener
299 * @see TextUI
300 * @see View
301 * @see ViewFactory
302 */
303 public abstract class JTextComponent extends JComponent implements Scrollable, Accessible
304 {
305 /**
306 * Creates a new <code>JTextComponent</code>.
307 * Listeners for caret events are established, and the pluggable
308 * UI installed. The component is marked as editable. No layout manager
309 * is used, because layout is managed by the view subsystem of text.
310 * The document model is set to <code>null</code>.
311 */
312 public JTextComponent() {
313 super();
314 // enable InputMethodEvent for on-the-spot pre-editing
315 enableEvents(AWTEvent.KEY_EVENT_MASK | AWTEvent.INPUT_METHOD_EVENT_MASK);
316 caretEvent = new MutableCaretEvent(this);
317 addMouseListener(caretEvent);
318 addFocusListener(caretEvent);
319 setEditable(true);
320 setDragEnabled(false);
321 setLayout(null); // layout is managed by View hierarchy
322 updateUI();
323 }
324
325 /**
326 * Fetches the user-interface factory for this text-oriented editor.
327 *
328 * @return the factory
329 */
330 public TextUI getUI() { return (TextUI)ui; }
331
332 /**
333 * Sets the user-interface factory for this text-oriented editor.
334 *
335 * @param ui the factory
336 */
337 public void setUI(TextUI ui) {
338 super.setUI(ui);
339 }
340
341 /**
342 * Reloads the pluggable UI. The key used to fetch the
343 * new interface is <code>getUIClassID()</code>. The type of
344 * the UI is <code>TextUI</code>. <code>invalidate</code>
345 * is called after setting the UI.
346 */
347 public void updateUI() {
348 setUI((TextUI)UIManager.getUI(this));
349 invalidate();
350 }
351
352 /**
353 * Adds a caret listener for notification of any changes
354 * to the caret.
355 *
356 * @param listener the listener to be added
357 * @see javax.swing.event.CaretEvent
358 */
359 public void addCaretListener(CaretListener listener) {
360 listenerList.add(CaretListener.class, listener);
361 }
362
363 /**
364 * Removes a caret listener.
365 *
366 * @param listener the listener to be removed
367 * @see javax.swing.event.CaretEvent
368 */
369 public void removeCaretListener(CaretListener listener) {
370 listenerList.remove(CaretListener.class, listener);
371 }
372
373 /**
374 * Returns an array of all the caret listeners
375 * registered on this text component.
376 *
377 * @return all of this component's <code>CaretListener</code>s
378 * or an empty
379 * array if no caret listeners are currently registered
380 *
381 * @see #addCaretListener
382 * @see #removeCaretListener
383 *
384 * @since 1.4
385 */
386 public CaretListener[] getCaretListeners() {
387 return listenerList.getListeners(CaretListener.class);
388 }
389
390 /**
391 * Notifies all listeners that have registered interest for
392 * notification on this event type. The event instance
393 * is lazily created using the parameters passed into
394 * the fire method. The listener list is processed in a
395 * last-to-first manner.
396 *
397 * @param e the event
398 * @see EventListenerList
399 */
400 protected void fireCaretUpdate(CaretEvent e) {
401 // Guaranteed to return a non-null array
402 Object[] listeners = listenerList.getListenerList();
403 // Process the listeners last to first, notifying
404 // those that are interested in this event
405 for (int i = listeners.length-2; i>=0; i-=2) {
406 if (listeners[i]==CaretListener.class) {
407 ((CaretListener)listeners[i+1]).caretUpdate(e);
408 }
409 }
410 }
411
412 /**
413 * Associates the editor with a text document.
414 * The currently registered factory is used to build a view for
415 * the document, which gets displayed by the editor after revalidation.
416 * A PropertyChange event ("document") is propagated to each listener.
417 *
418 * @param doc the document to display/edit
419 * @see #getDocument
420 * @beaninfo
421 * description: the text document model
422 * bound: true
423 * expert: true
424 */
425 public void setDocument(Document doc) {
426 Document old = model;
427
428 /*
429 * aquire a read lock on the old model to prevent notification of
430 * mutations while we disconnecting the old model.
431 */
432 try {
433 if (old instanceof AbstractDocument) {
434 ((AbstractDocument)old).readLock();
435 }
436 if (accessibleContext != null) {
437 model.removeDocumentListener(
438 ((AccessibleJTextComponent)accessibleContext));
439 }
440 if (inputMethodRequestsHandler != null) {
441 model.removeDocumentListener((DocumentListener)inputMethodRequestsHandler);
442 }
443 model = doc;
444
445 // Set the document's run direction property to match the
446 // component's ComponentOrientation property.
447 Boolean runDir = getComponentOrientation().isLeftToRight()
448 ? TextAttribute.RUN_DIRECTION_LTR
449 : TextAttribute.RUN_DIRECTION_RTL;
450 if (runDir != doc.getProperty(TextAttribute.RUN_DIRECTION)) {
451 doc.putProperty(TextAttribute.RUN_DIRECTION, runDir );
452 }
453 firePropertyChange("document", old, doc);
454 } finally {
455 if (old instanceof AbstractDocument) {
456 ((AbstractDocument)old).readUnlock();
457 }
458 }
459
460 revalidate();
461 repaint();
462 if (accessibleContext != null) {
463 model.addDocumentListener(
464 ((AccessibleJTextComponent)accessibleContext));
465 }
466 if (inputMethodRequestsHandler != null) {
467 model.addDocumentListener((DocumentListener)inputMethodRequestsHandler);
468 }
469 }
470
471 /**
472 * Fetches the model associated with the editor. This is
473 * primarily for the UI to get at the minimal amount of
474 * state required to be a text editor. Subclasses will
475 * return the actual type of the model which will typically
476 * be something that extends Document.
477 *
478 * @return the model
479 */
480 public Document getDocument() {
481 return model;
482 }
483
484 // Override of Component.setComponentOrientation
485 public void setComponentOrientation( ComponentOrientation o ) {
486 // Set the document's run direction property to match the
487 // ComponentOrientation property.
488 Document doc = getDocument();
489 if( doc != null ) {
490 Boolean runDir = o.isLeftToRight()
491 ? TextAttribute.RUN_DIRECTION_LTR
492 : TextAttribute.RUN_DIRECTION_RTL;
493 doc.putProperty( TextAttribute.RUN_DIRECTION, runDir );
494 }
495 super.setComponentOrientation( o );
496 }
497
498 /**
499 * Fetches the command list for the editor. This is
500 * the list of commands supported by the plugged-in UI
501 * augmented by the collection of commands that the
502 * editor itself supports. These are useful for binding
503 * to events, such as in a keymap.
504 *
505 * @return the command list
506 */
507 public Action[] getActions() {
508 return getUI().getEditorKit(this).getActions();
509 }
510
511 /**
512 * Sets margin space between the text component's border
513 * and its text. The text component's default <code>Border</code>
514 * object will use this value to create the proper margin.
515 * However, if a non-default border is set on the text component,
516 * it is that <code>Border</code> object's responsibility to create the
517 * appropriate margin space (else this property will effectively
518 * be ignored). This causes a redraw of the component.
519 * A PropertyChange event ("margin") is sent to all listeners.
520 *
521 * @param m the space between the border and the text
522 * @beaninfo
523 * description: desired space between the border and text area
524 * bound: true
525 */
526 public void setMargin(Insets m) {
527 Insets old = margin;
528 margin = m;
529 firePropertyChange("margin", old, m);
530 invalidate();
531 }
532
533 /**
534 * Returns the margin between the text component's border and
535 * its text.
536 *
537 * @return the margin
538 */
539 public Insets getMargin() {
540 return margin;
541 }
542
543 /**
544 * Sets the <code>NavigationFilter</code>. <code>NavigationFilter</code>
545 * is used by <code>DefaultCaret</code> and the default cursor movement
546 * actions as a way to restrict the cursor movement.
547 *
548 * @since 1.4
549 */
550 public void setNavigationFilter(NavigationFilter filter) {
551 navigationFilter = filter;
552 }
553
554 /**
555 * Returns the <code>NavigationFilter</code>. <code>NavigationFilter</code>
556 * is used by <code>DefaultCaret</code> and the default cursor movement
557 * actions as a way to restrict the cursor movement. A null return value
558 * implies the cursor movement and selection should not be restricted.
559 *
560 * @since 1.4
561 * @return the NavigationFilter
562 */
563 public NavigationFilter getNavigationFilter() {
564 return navigationFilter;
565 }
566
567 /**
568 * Fetches the caret that allows text-oriented navigation over
569 * the view.
570 *
571 * @return the caret
572 */
573 @Transient
574 public Caret getCaret() {
575 return caret;
576 }
577
578 /**
579 * Sets the caret to be used. By default this will be set
580 * by the UI that gets installed. This can be changed to
581 * a custom caret if desired. Setting the caret results in a
582 * PropertyChange event ("caret") being fired.
583 *
584 * @param c the caret
585 * @see #getCaret
586 * @beaninfo
587 * description: the caret used to select/navigate
588 * bound: true
589 * expert: true
590 */
591 public void setCaret(Caret c) {
592 if (caret != null) {
593 caret.removeChangeListener(caretEvent);
594 caret.deinstall(this);
595 }
596 Caret old = caret;
597 caret = c;
598 if (caret != null) {
599 caret.install(this);
600 caret.addChangeListener(caretEvent);
601 }
602 firePropertyChange("caret", old, caret);
603 }
604
605 /**
606 * Fetches the object responsible for making highlights.
607 *
608 * @return the highlighter
609 */
610 public Highlighter getHighlighter() {
611 return highlighter;
612 }
613
614 /**
615 * Sets the highlighter to be used. By default this will be set
616 * by the UI that gets installed. This can be changed to
617 * a custom highlighter if desired. The highlighter can be set to
618 * <code>null</code> to disable it.
619 * A PropertyChange event ("highlighter") is fired
620 * when a new highlighter is installed.
621 *
622 * @param h the highlighter
623 * @see #getHighlighter
624 * @beaninfo
625 * description: object responsible for background highlights
626 * bound: true
627 * expert: true
628 */
629 public void setHighlighter(Highlighter h) {
630 if (highlighter != null) {
631 highlighter.deinstall(this);
632 }
633 Highlighter old = highlighter;
634 highlighter = h;
635 if (highlighter != null) {
636 highlighter.install(this);
637 }
638 firePropertyChange("highlighter", old, h);
639 }
640
641 /**
642 * Sets the keymap to use for binding events to
643 * actions. Setting to <code>null</code> effectively disables
644 * keyboard input.
645 * A PropertyChange event ("keymap") is fired when a new keymap
646 * is installed.
647 *
648 * @param map the keymap
649 * @see #getKeymap
650 * @beaninfo
651 * description: set of key event to action bindings to use
652 * bound: true
653 */
654 public void setKeymap(Keymap map) {
655 Keymap old = keymap;
656 keymap = map;
657 firePropertyChange("keymap", old, keymap);
658 updateInputMap(old, map);
659 }
660
661 /**
662 * Turns on or off automatic drag handling. In order to enable automatic
663 * drag handling, this property should be set to {@code true}, and the
664 * component's {@code TransferHandler} needs to be {@code non-null}.
665 * The default value of the {@code dragEnabled} property is {@code false}.
666 * <p>
667 * The job of honoring this property, and recognizing a user drag gesture,
668 * lies with the look and feel implementation, and in particular, the component's
669 * {@code TextUI}. When automatic drag handling is enabled, most look and
670 * feels (including those that subclass {@code BasicLookAndFeel}) begin a
671 * drag and drop operation whenever the user presses the mouse button over
672 * a selection and then moves the mouse a few pixels. Setting this property to
673 * {@code true} can therefore have a subtle effect on how selections behave.
674 * <p>
675 * If a look and feel is used that ignores this property, you can still
676 * begin a drag and drop operation by calling {@code exportAsDrag} on the
677 * component's {@code TransferHandler}.
678 *
679 * @param b whether or not to enable automatic drag handling
680 * @exception HeadlessException if
681 * <code>b</code> is <code>true</code> and
682 * <code>GraphicsEnvironment.isHeadless()</code>
683 * returns <code>true</code>
684 * @see java.awt.GraphicsEnvironment#isHeadless
685 * @see #getDragEnabled
686 * @see #setTransferHandler
687 * @see TransferHandler
688 * @since 1.4
689 *
690 * @beaninfo
691 * description: determines whether automatic drag handling is enabled
692 * bound: false
693 */
694 public void setDragEnabled(boolean b) {
695 if (b && GraphicsEnvironment.isHeadless()) {
696 throw new HeadlessException();
697 }
698 dragEnabled = b;
699 }
700
701 /**
702 * Returns whether or not automatic drag handling is enabled.
703 *
704 * @return the value of the {@code dragEnabled} property
705 * @see #setDragEnabled
706 * @since 1.4
707 */
708 public boolean getDragEnabled() {
709 return dragEnabled;
710 }
711
712 /**
713 * Sets the drop mode for this component. For backward compatibility,
714 * the default for this property is <code>DropMode.USE_SELECTION</code>.
715 * Usage of <code>DropMode.INSERT</code> is recommended, however,
716 * for an improved user experience. It offers similar behavior of dropping
717 * between text locations, but does so without affecting the actual text
718 * selection and caret location.
719 * <p>
720 * <code>JTextComponents</code> support the following drop modes:
721 * <ul>
722 * <li><code>DropMode.USE_SELECTION</code></li>
723 * <li><code>DropMode.INSERT</code></li>
724 * </ul>
725 * <p>
726 * The drop mode is only meaningful if this component has a
727 * <code>TransferHandler</code> that accepts drops.
728 *
729 * @param dropMode the drop mode to use
730 * @throws IllegalArgumentException if the drop mode is unsupported
731 * or <code>null</code>
732 * @see #getDropMode
733 * @see #getDropLocation
734 * @see #setTransferHandler
735 * @see javax.swing.TransferHandler
736 * @since 1.6
737 */
738 public final void setDropMode(DropMode dropMode) {
739 if (dropMode != null) {
740 switch (dropMode) {
741 case USE_SELECTION:
742 case INSERT:
743 this.dropMode = dropMode;
744 return;
745 }
746 }
747
748 throw new IllegalArgumentException(dropMode + ": Unsupported drop mode for text");
749 }
750
751 /**
752 * Returns the drop mode for this component.
753 *
754 * @return the drop mode for this component
755 * @see #setDropMode
756 * @since 1.6
757 */
758 public final DropMode getDropMode() {
759 return dropMode;
760 }
761
762 static {
763 SwingAccessor.setJTextComponentAccessor(
764 new SwingAccessor.JTextComponentAccessor() {
765 public TransferHandler.DropLocation dropLocationForPoint(JTextComponent textComp,
766 Point p)
767 {
768 return textComp.dropLocationForPoint(p);
769 }
770 public Object setDropLocation(JTextComponent textComp,
771 TransferHandler.DropLocation location,
772 Object state, boolean forDrop)
773 {
774 return textComp.setDropLocation(location, state, forDrop);
775 }
776 });
777 }
778
779
780 /**
781 * Calculates a drop location in this component, representing where a
782 * drop at the given point should insert data.
783 * <p>
784 * Note: This method is meant to override
785 * <code>JComponent.dropLocationForPoint()</code>, which is package-private
786 * in javax.swing. <code>TransferHandler</code> will detect text components
787 * and call this method instead via reflection. It's name should therefore
788 * not be changed.
789 *
790 * @param p the point to calculate a drop location for
791 * @return the drop location, or <code>null</code>
792 */
793 DropLocation dropLocationForPoint(Point p) {
794 Position.Bias[] bias = new Position.Bias[1];
795 int index = getUI().viewToModel(this, p, bias);
796
797 // viewToModel currently returns null for some HTML content
798 // when the point is within the component's top inset
799 if (bias[0] == null) {
800 bias[0] = Position.Bias.Forward;
801 }
802
803 return new DropLocation(p, index, bias[0]);
804 }
805
806 /**
807 * Called to set or clear the drop location during a DnD operation.
808 * In some cases, the component may need to use it's internal selection
809 * temporarily to indicate the drop location. To help facilitate this,
810 * this method returns and accepts as a parameter a state object.
811 * This state object can be used to store, and later restore, the selection
812 * state. Whatever this method returns will be passed back to it in
813 * future calls, as the state parameter. If it wants the DnD system to
814 * continue storing the same state, it must pass it back every time.
815 * Here's how this is used:
816 * <p>
817 * Let's say that on the first call to this method the component decides
818 * to save some state (because it is about to use the selection to show
819 * a drop index). It can return a state object to the caller encapsulating
820 * any saved selection state. On a second call, let's say the drop location
821 * is being changed to something else. The component doesn't need to
822 * restore anything yet, so it simply passes back the same state object
823 * to have the DnD system continue storing it. Finally, let's say this
824 * method is messaged with <code>null</code>. This means DnD
825 * is finished with this component for now, meaning it should restore
826 * state. At this point, it can use the state parameter to restore
827 * said state, and of course return <code>null</code> since there's
828 * no longer anything to store.
829 * <p>
830 * Note: This method is meant to override
831 * <code>JComponent.setDropLocation()</code>, which is package-private
832 * in javax.swing. <code>TransferHandler</code> will detect text components
833 * and call this method instead via reflection. It's name should therefore
834 * not be changed.
835 *
836 * @param location the drop location (as calculated by
837 * <code>dropLocationForPoint</code>) or <code>null</code>
838 * if there's no longer a valid drop location
839 * @param state the state object saved earlier for this component,
840 * or <code>null</code>
841 * @param forDrop whether or not the method is being called because an
842 * actual drop occurred
843 * @return any saved state for this component, or <code>null</code> if none
844 */
845 Object setDropLocation(TransferHandler.DropLocation location,
846 Object state,
847 boolean forDrop) {
848
849 Object retVal = null;
850 DropLocation textLocation = (DropLocation)location;
851
852 if (dropMode == DropMode.USE_SELECTION) {
853 if (textLocation == null) {
854 if (state != null) {
855 /*
856 * This object represents the state saved earlier.
857 * If the caret is a DefaultCaret it will be
858 * an Object array containing, in order:
859 * - the saved caret mark (Integer)
860 * - the saved caret dot (Integer)
861 * - the saved caret visibility (Boolean)
862 * - the saved mark bias (Position.Bias)
863 * - the saved dot bias (Position.Bias)
864 * If the caret is not a DefaultCaret it will
865 * be similar, but will not contain the dot
866 * or mark bias.
867 */
868 Object[] vals = (Object[])state;
869
870 if (!forDrop) {
871 if (caret instanceof DefaultCaret) {
872 ((DefaultCaret)caret).setDot(((Integer)vals[0]).intValue(),
873 (Position.Bias)vals[3]);
874 ((DefaultCaret)caret).moveDot(((Integer)vals[1]).intValue(),
875 (Position.Bias)vals[4]);
876 } else {
877 caret.setDot(((Integer)vals[0]).intValue());
878 caret.moveDot(((Integer)vals[1]).intValue());
879 }
880 }
881
882 caret.setVisible(((Boolean)vals[2]).booleanValue());
883 }
884 } else {
885 if (dropLocation == null) {
886 boolean visible;
887
888 if (caret instanceof DefaultCaret) {
889 DefaultCaret dc = (DefaultCaret)caret;
890 visible = dc.isActive();
891 retVal = new Object[] {Integer.valueOf(dc.getMark()),
892 Integer.valueOf(dc.getDot()),
893 Boolean.valueOf(visible),
894 dc.getMarkBias(),
895 dc.getDotBias()};
896 } else {
897 visible = caret.isVisible();
898 retVal = new Object[] {Integer.valueOf(caret.getMark()),
899 Integer.valueOf(caret.getDot()),
900 Boolean.valueOf(visible)};
901 }
902
903 caret.setVisible(true);
904 } else {
905 retVal = state;
906 }
907
908 if (caret instanceof DefaultCaret) {
909 ((DefaultCaret)caret).setDot(textLocation.getIndex(), textLocation.getBias());
910 } else {
911 caret.setDot(textLocation.getIndex());
912 }
913 }
914 } else {
915 if (textLocation == null) {
916 if (state != null) {
917 caret.setVisible(((Boolean)state).booleanValue());
918 }
919 } else {
920 if (dropLocation == null) {
921 boolean visible = caret instanceof DefaultCaret
922 ? ((DefaultCaret)caret).isActive()
923 : caret.isVisible();
924 retVal = Boolean.valueOf(visible);
925 caret.setVisible(false);
926 } else {
927 retVal = state;
928 }
929 }
930 }
931
932 DropLocation old = dropLocation;
933 dropLocation = textLocation;
934 firePropertyChange("dropLocation", old, dropLocation);
935
936 return retVal;
937 }
938
939 /**
940 * Returns the location that this component should visually indicate
941 * as the drop location during a DnD operation over the component,
942 * or {@code null} if no location is to currently be shown.
943 * <p>
944 * This method is not meant for querying the drop location
945 * from a {@code TransferHandler}, as the drop location is only
946 * set after the {@code TransferHandler}'s <code>canImport</code>
947 * has returned and has allowed for the location to be shown.
948 * <p>
949 * When this property changes, a property change event with
950 * name "dropLocation" is fired by the component.
951 *
952 * @return the drop location
953 * @see #setDropMode
954 * @see TransferHandler#canImport(TransferHandler.TransferSupport)
955 * @since 1.6
956 */
957 public final DropLocation getDropLocation() {
958 return dropLocation;
959 }
960
961
962 /**
963 * Updates the <code>InputMap</code>s in response to a
964 * <code>Keymap</code> change.
965 * @param oldKm the old <code>Keymap</code>
966 * @param newKm the new <code>Keymap</code>
967 */
968 void updateInputMap(Keymap oldKm, Keymap newKm) {
969 // Locate the current KeymapWrapper.
970 InputMap km = getInputMap(JComponent.WHEN_FOCUSED);
971 InputMap last = km;
972 while (km != null && !(km instanceof KeymapWrapper)) {
973 last = km;
974 km = km.getParent();
975 }
976 if (km != null) {
977 // Found it, tweak the InputMap that points to it, as well
978 // as anything it points to.
979 if (newKm == null) {
980 if (last != km) {
981 last.setParent(km.getParent());
982 }
983 else {
984 last.setParent(null);
985 }
986 }
987 else {
988 InputMap newKM = new KeymapWrapper(newKm);
989 last.setParent(newKM);
990 if (last != km) {
991 newKM.setParent(km.getParent());
992 }
993 }
994 }
995 else if (newKm != null) {
996 km = getInputMap(JComponent.WHEN_FOCUSED);
997 if (km != null) {
998 // Couldn't find it.
999 // Set the parent of WHEN_FOCUSED InputMap to be the new one.
1000 InputMap newKM = new KeymapWrapper(newKm);
1001 newKM.setParent(km.getParent());
1002 km.setParent(newKM);
1003 }
1004 }
1005
1006 // Do the same thing with the ActionMap
1007 ActionMap am = getActionMap();
1008 ActionMap lastAM = am;
1009 while (am != null && !(am instanceof KeymapActionMap)) {
1010 lastAM = am;
1011 am = am.getParent();
1012 }
1013 if (am != null) {
1014 // Found it, tweak the Actionap that points to it, as well
1015 // as anything it points to.
1016 if (newKm == null) {
1017 if (lastAM != am) {
1018 lastAM.setParent(am.getParent());
1019 }
1020 else {
1021 lastAM.setParent(null);
1022 }
1023 }
1024 else {
1025 ActionMap newAM = new KeymapActionMap(newKm);
1026 lastAM.setParent(newAM);
1027 if (lastAM != am) {
1028 newAM.setParent(am.getParent());
1029 }
1030 }
1031 }
1032 else if (newKm != null) {
1033 am = getActionMap();
1034 if (am != null) {
1035 // Couldn't find it.
1036 // Set the parent of ActionMap to be the new one.
1037 ActionMap newAM = new KeymapActionMap(newKm);
1038 newAM.setParent(am.getParent());
1039 am.setParent(newAM);
1040 }
1041 }
1042 }
1043
1044 /**
1045 * Fetches the keymap currently active in this text
1046 * component.
1047 *
1048 * @return the keymap
1049 */
1050 public Keymap getKeymap() {
1051 return keymap;
1052 }
1053
1054 /**
1055 * Adds a new keymap into the keymap hierarchy. Keymap bindings
1056 * resolve from bottom up so an attribute specified in a child
1057 * will override an attribute specified in the parent.
1058 *
1059 * @param nm the name of the keymap (must be unique within the
1060 * collection of named keymaps in the document); the name may
1061 * be <code>null</code> if the keymap is unnamed,
1062 * but the caller is responsible for managing the reference
1063 * returned as an unnamed keymap can't
1064 * be fetched by name
1065 * @param parent the parent keymap; this may be <code>null</code> if
1066 * unspecified bindings need not be resolved in some other keymap
1067 * @return the keymap
1068 */
1069 public static Keymap addKeymap(String nm, Keymap parent) {
1070 Keymap map = new DefaultKeymap(nm, parent);
1071 if (nm != null) {
1072 // add a named keymap, a class of bindings
1073 getKeymapTable().put(nm, map);
1074 }
1075 return map;
1076 }
1077
1078 /**
1079 * Removes a named keymap previously added to the document. Keymaps
1080 * with <code>null</code> names may not be removed in this way.
1081 *
1082 * @param nm the name of the keymap to remove
1083 * @return the keymap that was removed
1084 */
1085 public static Keymap removeKeymap(String nm) {
1086 return getKeymapTable().remove(nm);
1087 }
1088
1089 /**
1090 * Fetches a named keymap previously added to the document.
1091 * This does not work with <code>null</code>-named keymaps.
1092 *
1093 * @param nm the name of the keymap
1094 * @return the keymap
1095 */
1096 public static Keymap getKeymap(String nm) {
1097 return getKeymapTable().get(nm);
1098 }
1099
1100 private static HashMap<String,Keymap> getKeymapTable() {
1101 synchronized (KEYMAP_TABLE) {
1102 AppContext appContext = AppContext.getAppContext();
1103 HashMap<String,Keymap> keymapTable =
1104 (HashMap<String,Keymap>)appContext.get(KEYMAP_TABLE);
1105 if (keymapTable == null) {
1106 keymapTable = new HashMap<String,Keymap>(17);
1107 appContext.put(KEYMAP_TABLE, keymapTable);
1108 //initialize default keymap
1109 Keymap binding = addKeymap(DEFAULT_KEYMAP, null);
1110 binding.setDefaultAction(new
1111 DefaultEditorKit.DefaultKeyTypedAction());
1112 }
1113 return keymapTable;
1114 }
1115 }
1116
1117 /**
1118 * Binding record for creating key bindings.
1119 * <p>
1120 * <strong>Warning:</strong>
1121 * Serialized objects of this class will not be compatible with
1122 * future Swing releases. The current serialization support is
1123 * appropriate for short term storage or RMI between applications running
1124 * the same version of Swing. As of 1.4, support for long term storage
1125 * of all JavaBeans<sup><font size="-2">TM</font></sup>
1126 * has been added to the <code>java.beans</code> package.
1127 * Please see {@link java.beans.XMLEncoder}.
1128 */
1129 public static class KeyBinding {
1130
1131 /**
1132 * The key.
1133 */
1134 public KeyStroke key;
1135
1136 /**
1137 * The name of the action for the key.
1138 */
1139 public String actionName;
1140
1141 /**
1142 * Creates a new key binding.
1143 *
1144 * @param key the key
1145 * @param actionName the name of the action for the key
1146 */
1147 public KeyBinding(KeyStroke key, String actionName) {
1148 this.key = key;
1149 this.actionName = actionName;
1150 }
1151 }
1152
1153 /**
1154 * <p>
1155 * Loads a keymap with a bunch of
1156 * bindings. This can be used to take a static table of
1157 * definitions and load them into some keymap. The following
1158 * example illustrates an example of binding some keys to
1159 * the cut, copy, and paste actions associated with a
1160 * JTextComponent. A code fragment to accomplish
1161 * this might look as follows:
1162 * <pre><code>
1163 *
1164 * static final JTextComponent.KeyBinding[] defaultBindings = {
1165 * new JTextComponent.KeyBinding(
1166 * KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_MASK),
1167 * DefaultEditorKit.copyAction),
1168 * new JTextComponent.KeyBinding(
1169 * KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_MASK),
1170 * DefaultEditorKit.pasteAction),
1171 * new JTextComponent.KeyBinding(
1172 * KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.CTRL_MASK),
1173 * DefaultEditorKit.cutAction),
1174 * };
1175 *
1176 * JTextComponent c = new JTextPane();
1177 * Keymap k = c.getKeymap();
1178 * JTextComponent.loadKeymap(k, defaultBindings, c.getActions());
1179 *
1180 * </code></pre>
1181 * The sets of bindings and actions may be empty but must be
1182 * non-<code>null</code>.
1183 *
1184 * @param map the keymap
1185 * @param bindings the bindings
1186 * @param actions the set of actions
1187 */
1188 public static void loadKeymap(Keymap map, KeyBinding[] bindings, Action[] actions) {
1189 Hashtable<String, Action> h = new Hashtable<String, Action>();
1190 for (Action a : actions) {
1191 String value = (String)a.getValue(Action.NAME);
1192 h.put((value!=null ? value:""), a);
1193 }
1194 for (KeyBinding binding : bindings) {
1195 Action a = h.get(binding.actionName);
1196 if (a != null) {
1197 map.addActionForKeyStroke(binding.key, a);
1198 }
1199 }
1200 }
1201
1202 /**
1203 * Returns true if <code>klass</code> is NOT a JTextComponent and it or
1204 * one of its superclasses (stoping at JTextComponent) overrides
1205 * <code>processInputMethodEvent</code>. It is assumed this will be
1206 * invoked from within a <code>doPrivileged</code>, and it is also
1207 * assumed <code>klass</code> extends <code>JTextComponent</code>.
1208 */
1209 private static Boolean isProcessInputMethodEventOverridden(Class<?> klass) {
1210 if (klass == JTextComponent.class) {
1211 return Boolean.FALSE;
1212 }
1213 Boolean retValue = overrideMap.get(klass.getName());
1214
1215 if (retValue != null) {
1216 return retValue;
1217 }
1218 Boolean sOverriden = isProcessInputMethodEventOverridden(
1219 klass.getSuperclass());
1220
1221 if (sOverriden.booleanValue()) {
1222 // If our superclass has overriden it, then by definition klass
1223 // overrides it.
1224 overrideMap.put(klass.getName(), sOverriden);
1225 return sOverriden;
1226 }
1227 // klass's superclass didn't override it, check for an override in
1228 // klass.
1229 try {
1230 Class[] classes = new Class[1];
1231 classes[0] = InputMethodEvent.class;
1232
1233 Method m = klass.getDeclaredMethod("processInputMethodEvent",
1234 classes);
1235 retValue = Boolean.TRUE;
1236 } catch (NoSuchMethodException nsme) {
1237 retValue = Boolean.FALSE;
1238 }
1239 overrideMap.put(klass.getName(), retValue);
1240 return retValue;
1241 }
1242
1243 /**
1244 * Fetches the current color used to render the
1245 * caret.
1246 *
1247 * @return the color
1248 */
1249 public Color getCaretColor() {
1250 return caretColor;
1251 }
1252
1253 /**
1254 * Sets the current color used to render the caret.
1255 * Setting to <code>null</code> effectively restores the default color.
1256 * Setting the color results in a PropertyChange event ("caretColor")
1257 * being fired.
1258 *
1259 * @param c the color
1260 * @see #getCaretColor
1261 * @beaninfo
1262 * description: the color used to render the caret
1263 * bound: true
1264 * preferred: true
1265 */
1266 public void setCaretColor(Color c) {
1267 Color old = caretColor;
1268 caretColor = c;
1269 firePropertyChange("caretColor", old, caretColor);
1270 }
1271
1272 /**
1273 * Fetches the current color used to render the
1274 * selection.
1275 *
1276 * @return the color
1277 */
1278 public Color getSelectionColor() {
1279 return selectionColor;
1280 }
1281
1282 /**
1283 * Sets the current color used to render the selection.
1284 * Setting the color to <code>null</code> is the same as setting
1285 * <code>Color.white</code>. Setting the color results in a
1286 * PropertyChange event ("selectionColor").
1287 *
1288 * @param c the color
1289 * @see #getSelectionColor
1290 * @beaninfo
1291 * description: color used to render selection background
1292 * bound: true
1293 * preferred: true
1294 */
1295 public void setSelectionColor(Color c) {
1296 Color old = selectionColor;
1297 selectionColor = c;
1298 firePropertyChange("selectionColor", old, selectionColor);
1299 }
1300
1301 /**
1302 * Fetches the current color used to render the
1303 * selected text.
1304 *
1305 * @return the color
1306 */
1307 public Color getSelectedTextColor() {
1308 return selectedTextColor;
1309 }
1310
1311 /**
1312 * Sets the current color used to render the selected text.
1313 * Setting the color to <code>null</code> is the same as
1314 * <code>Color.black</code>. Setting the color results in a
1315 * PropertyChange event ("selectedTextColor") being fired.
1316 *
1317 * @param c the color
1318 * @see #getSelectedTextColor
1319 * @beaninfo
1320 * description: color used to render selected text
1321 * bound: true
1322 * preferred: true
1323 */
1324 public void setSelectedTextColor(Color c) {
1325 Color old = selectedTextColor;
1326 selectedTextColor = c;
1327 firePropertyChange("selectedTextColor", old, selectedTextColor);
1328 }
1329
1330 /**
1331 * Fetches the current color used to render the
1332 * disabled text.
1333 *
1334 * @return the color
1335 */
1336 public Color getDisabledTextColor() {
1337 return disabledTextColor;
1338 }
1339
1340 /**
1341 * Sets the current color used to render the
1342 * disabled text. Setting the color fires off a
1343 * PropertyChange event ("disabledTextColor").
1344 *
1345 * @param c the color
1346 * @see #getDisabledTextColor
1347 * @beaninfo
1348 * description: color used to render disabled text
1349 * bound: true
1350 * preferred: true
1351 */
1352 public void setDisabledTextColor(Color c) {
1353 Color old = disabledTextColor;
1354 disabledTextColor = c;
1355 firePropertyChange("disabledTextColor", old, disabledTextColor);
1356 }
1357
1358 /**
1359 * Replaces the currently selected content with new content
1360 * represented by the given string. If there is no selection
1361 * this amounts to an insert of the given text. If there
1362 * is no replacement text this amounts to a removal of the
1363 * current selection.
1364 * <p>
1365 * This is the method that is used by the default implementation
1366 * of the action for inserting content that gets bound to the
1367 * keymap actions.
1368 *
1369 * @param content the content to replace the selection with
1370 */
1371 public void replaceSelection(String content) {
1372 Document doc = getDocument();
1373 if (doc != null) {
1374 try {
1375 boolean composedTextSaved = saveComposedText(caret.getDot());
1376 int p0 = Math.min(caret.getDot(), caret.getMark());
1377 int p1 = Math.max(caret.getDot(), caret.getMark());
1378 if (doc instanceof AbstractDocument) {
1379 ((AbstractDocument)doc).replace(p0, p1 - p0, content,null);
1380 }
1381 else {
1382 if (p0 != p1) {
1383 doc.remove(p0, p1 - p0);
1384 }
1385 if (content != null && content.length() > 0) {
1386 doc.insertString(p0, content, null);
1387 }
1388 }
1389 if (composedTextSaved) {
1390 restoreComposedText();
1391 }
1392 } catch (BadLocationException e) {
1393 UIManager.getLookAndFeel().provideErrorFeedback(JTextComponent.this);
1394 }
1395 }
1396 }
1397
1398 /**
1399 * Fetches a portion of the text represented by the
1400 * component. Returns an empty string if length is 0.
1401 *
1402 * @param offs the offset >= 0
1403 * @param len the length >= 0
1404 * @return the text
1405 * @exception BadLocationException if the offset or length are invalid
1406 */
1407 public String getText(int offs, int len) throws BadLocationException {
1408 return getDocument().getText(offs, len);
1409 }
1410
1411 /**
1412 * Converts the given location in the model to a place in
1413 * the view coordinate system.
1414 * The component must have a positive size for
1415 * this translation to be computed (i.e. layout cannot
1416 * be computed until the component has been sized). The
1417 * component does not have to be visible or painted.
1418 *
1419 * @param pos the position >= 0
1420 * @return the coordinates as a rectangle, with (r.x, r.y) as the location
1421 * in the coordinate system, or null if the component does
1422 * not yet have a positive size.
1423 * @exception BadLocationException if the given position does not
1424 * represent a valid location in the associated document
1425 * @see TextUI#modelToView
1426 */
1427 public Rectangle modelToView(int pos) throws BadLocationException {
1428 return getUI().modelToView(this, pos);
1429 }
1430
1431 /**
1432 * Converts the given place in the view coordinate system
1433 * to the nearest representative location in the model.
1434 * The component must have a positive size for
1435 * this translation to be computed (i.e. layout cannot
1436 * be computed until the component has been sized). The
1437 * component does not have to be visible or painted.
1438 *
1439 * @param pt the location in the view to translate
1440 * @return the offset >= 0 from the start of the document,
1441 * or -1 if the component does not yet have a positive
1442 * size.
1443 * @see TextUI#viewToModel
1444 */
1445 public int viewToModel(Point pt) {
1446 return getUI().viewToModel(this, pt);
1447 }
1448
1449 /**
1450 * Transfers the currently selected range in the associated
1451 * text model to the system clipboard, removing the contents
1452 * from the model. The current selection is reset. Does nothing
1453 * for <code>null</code> selections.
1454 *
1455 * @see java.awt.Toolkit#getSystemClipboard
1456 * @see java.awt.datatransfer.Clipboard
1457 */
1458 public void cut() {
1459 if (isEditable() && isEnabled()) {
1460 invokeAction("cut", TransferHandler.getCutAction());
1461 }
1462 }
1463
1464 /**
1465 * Transfers the currently selected range in the associated
1466 * text model to the system clipboard, leaving the contents
1467 * in the text model. The current selection remains intact.
1468 * Does nothing for <code>null</code> selections.
1469 *
1470 * @see java.awt.Toolkit#getSystemClipboard
1471 * @see java.awt.datatransfer.Clipboard
1472 */
1473 public void copy() {
1474 invokeAction("copy", TransferHandler.getCopyAction());
1475 }
1476
1477 /**
1478 * Transfers the contents of the system clipboard into the
1479 * associated text model. If there is a selection in the
1480 * associated view, it is replaced with the contents of the
1481 * clipboard. If there is no selection, the clipboard contents
1482 * are inserted in front of the current insert position in
1483 * the associated view. If the clipboard is empty, does nothing.
1484 *
1485 * @see #replaceSelection
1486 * @see java.awt.Toolkit#getSystemClipboard
1487 * @see java.awt.datatransfer.Clipboard
1488 */
1489 public void paste() {
1490 if (isEditable() && isEnabled()) {
1491 invokeAction("paste", TransferHandler.getPasteAction());
1492 }
1493 }
1494
1495 /**
1496 * This is a conveniance method that is only useful for
1497 * <code>cut</code>, <code>copy</code> and <code>paste</code>. If
1498 * an <code>Action</code> with the name <code>name</code> does not
1499 * exist in the <code>ActionMap</code>, this will attemp to install a
1500 * <code>TransferHandler</code> and then use <code>altAction</code>.
1501 */
1502 private void invokeAction(String name, Action altAction) {
1503 ActionMap map = getActionMap();
1504 Action action = null;
1505
1506 if (map != null) {
1507 action = map.get(name);
1508 }
1509 if (action == null) {
1510 installDefaultTransferHandlerIfNecessary();
1511 action = altAction;
1512 }
1513 action.actionPerformed(new ActionEvent(this,
1514 ActionEvent.ACTION_PERFORMED, (String)action.
1515 getValue(Action.NAME),
1516 EventQueue.getMostRecentEventTime(),
1517 getCurrentEventModifiers()));
1518 }
1519
1520 /**
1521 * If the current <code>TransferHandler</code> is null, this will
1522 * install a new one.
1523 */
1524 private void installDefaultTransferHandlerIfNecessary() {
1525 if (getTransferHandler() == null) {
1526 if (defaultTransferHandler == null) {
1527 defaultTransferHandler = new DefaultTransferHandler();
1528 }
1529 setTransferHandler(defaultTransferHandler);
1530 }
1531 }
1532
1533 /**
1534 * Moves the caret to a new position, leaving behind a mark
1535 * defined by the last time <code>setCaretPosition</code> was
1536 * called. This forms a selection.
1537 * If the document is <code>null</code>, does nothing. The position
1538 * must be between 0 and the length of the component's text or else
1539 * an exception is thrown.
1540 *
1541 * @param pos the position
1542 * @exception IllegalArgumentException if the value supplied
1543 * for <code>position</code> is less than zero or greater
1544 * than the component's text length
1545 * @see #setCaretPosition
1546 */
1547 public void moveCaretPosition(int pos) {
1548 Document doc = getDocument();
1549 if (doc != null) {
1550 if (pos > doc.getLength() || pos < 0) {
1551 throw new IllegalArgumentException("bad position: " + pos);
1552 }
1553 caret.moveDot(pos);
1554 }
1555 }
1556
1557 /**
1558 * The bound property name for the focus accelerator.
1559 */
1560 public static final String FOCUS_ACCELERATOR_KEY = "focusAcceleratorKey";
1561
1562 /**
1563 * Sets the key accelerator that will cause the receiving text
1564 * component to get the focus. The accelerator will be the
1565 * key combination of the <em>alt</em> key and the character
1566 * given (converted to upper case). By default, there is no focus
1567 * accelerator key. Any previous key accelerator setting will be
1568 * superseded. A '\0' key setting will be registered, and has the
1569 * effect of turning off the focus accelerator. When the new key
1570 * is set, a PropertyChange event (FOCUS_ACCELERATOR_KEY) will be fired.
1571 *
1572 * @param aKey the key
1573 * @see #getFocusAccelerator
1574 * @beaninfo
1575 * description: accelerator character used to grab focus
1576 * bound: true
1577 */
1578 public void setFocusAccelerator(char aKey) {
1579 aKey = Character.toUpperCase(aKey);
1580 char old = focusAccelerator;
1581 focusAccelerator = aKey;
1582 // Fix for 4341002: value of FOCUS_ACCELERATOR_KEY is wrong.
1583 // So we fire both FOCUS_ACCELERATOR_KEY, for compatibility,
1584 // and the correct event here.
1585 firePropertyChange(FOCUS_ACCELERATOR_KEY, old, focusAccelerator);
1586 firePropertyChange("focusAccelerator", old, focusAccelerator);
1587 }
1588
1589 /**
1590 * Returns the key accelerator that will cause the receiving
1591 * text component to get the focus. Return '\0' if no focus
1592 * accelerator has been set.
1593 *
1594 * @return the key
1595 */
1596 public char getFocusAccelerator() {
1597 return focusAccelerator;
1598 }
1599
1600 /**
1601 * Initializes from a stream. This creates a
1602 * model of the type appropriate for the component
1603 * and initializes the model from the stream.
1604 * By default this will load the model as plain
1605 * text. Previous contents of the model are discarded.
1606 *
1607 * @param in the stream to read from
1608 * @param desc an object describing the stream; this
1609 * might be a string, a File, a URL, etc. Some kinds
1610 * of documents (such as html for example) might be
1611 * able to make use of this information; if non-<code>null</code>,
1612 * it is added as a property of the document
1613 * @exception IOException as thrown by the stream being
1614 * used to initialize
1615 * @see EditorKit#createDefaultDocument
1616 * @see #setDocument
1617 * @see PlainDocument
1618 */
1619 public void read(Reader in, Object desc) throws IOException {
1620 EditorKit kit = getUI().getEditorKit(this);
1621 Document doc = kit.createDefaultDocument();
1622 if (desc != null) {
1623 doc.putProperty(Document.StreamDescriptionProperty, desc);
1624 }
1625 try {
1626 kit.read(in, doc, 0);
1627 setDocument(doc);
1628 } catch (BadLocationException e) {
1629 throw new IOException(e.getMessage());
1630 }
1631 }
1632
1633 /**
1634 * Stores the contents of the model into the given
1635 * stream. By default this will store the model as plain
1636 * text.
1637 *
1638 * @param out the output stream
1639 * @exception IOException on any I/O error
1640 */
1641 public void write(Writer out) throws IOException {
1642 Document doc = getDocument();
1643 try {
1644 getUI().getEditorKit(this).write(out, doc, 0, doc.getLength());
1645 } catch (BadLocationException e) {
1646 throw new IOException(e.getMessage());
1647 }
1648 }
1649
1650 public void removeNotify() {
1651 super.removeNotify();
1652 if (getFocusedComponent() == this) {
1653 AppContext.getAppContext().remove(FOCUSED_COMPONENT);
1654 }
1655 }
1656
1657 // --- java.awt.TextComponent methods ------------------------
1658
1659 /**
1660 * Sets the position of the text insertion caret for the
1661 * <code>TextComponent</code>. Note that the caret tracks change,
1662 * so this may move if the underlying text of the component is changed.
1663 * If the document is <code>null</code>, does nothing. The position
1664 * must be between 0 and the length of the component's text or else
1665 * an exception is thrown.
1666 *
1667 * @param position the position
1668 * @exception IllegalArgumentException if the value supplied
1669 * for <code>position</code> is less than zero or greater
1670 * than the component's text length
1671 * @beaninfo
1672 * description: the caret position
1673 */
1674 public void setCaretPosition(int position) {
1675 Document doc = getDocument();
1676 if (doc != null) {
1677 if (position > doc.getLength() || position < 0) {
1678 throw new IllegalArgumentException("bad position: " + position);
1679 }
1680 caret.setDot(position);
1681 }
1682 }
1683
1684 /**
1685 * Returns the position of the text insertion caret for the
1686 * text component.
1687 *
1688 * @return the position of the text insertion caret for the
1689 * text component >= 0
1690 */
1691 @Transient
1692 public int getCaretPosition() {
1693 return caret.getDot();
1694 }
1695
1696 /**
1697 * Sets the text of this <code>TextComponent</code>
1698 * to the specified text. If the text is <code>null</code>
1699 * or empty, has the effect of simply deleting the old text.
1700 * When text has been inserted, the resulting caret location
1701 * is determined by the implementation of the caret class.
1702 *
1703 * <p>
1704 * Note that text is not a bound property, so no <code>PropertyChangeEvent
1705 * </code> is fired when it changes. To listen for changes to the text,
1706 * use <code>DocumentListener</code>.
1707 *
1708 * @param t the new text to be set
1709 * @see #getText
1710 * @see DefaultCaret
1711 * @beaninfo
1712 * description: the text of this component
1713 */
1714 public void setText(String t) {
1715 try {
1716 Document doc = getDocument();
1717 if (doc instanceof AbstractDocument) {
1718 ((AbstractDocument)doc).replace(0, doc.getLength(), t,null);
1719 }
1720 else {
1721 doc.remove(0, doc.getLength());
1722 doc.insertString(0, t, null);
1723 }
1724 } catch (BadLocationException e) {
1725 UIManager.getLookAndFeel().provideErrorFeedback(JTextComponent.this);
1726 }
1727 }
1728
1729 /**
1730 * Returns the text contained in this <code>TextComponent</code>.
1731 * If the underlying document is <code>null</code>,
1732 * will give a <code>NullPointerException</code>.
1733 *
1734 * Note that text is not a bound property, so no <code>PropertyChangeEvent
1735 * </code> is fired when it changes. To listen for changes to the text,
1736 * use <code>DocumentListener</code>.
1737 *
1738 * @return the text
1739 * @exception NullPointerException if the document is <code>null</code>
1740 * @see #setText
1741 */
1742 public String getText() {
1743 Document doc = getDocument();
1744 String txt;
1745 try {
1746 txt = doc.getText(0, doc.getLength());
1747 } catch (BadLocationException e) {
1748 txt = null;
1749 }
1750 return txt;
1751 }
1752
1753 /**
1754 * Returns the selected text contained in this
1755 * <code>TextComponent</code>. If the selection is
1756 * <code>null</code> or the document empty, returns <code>null</code>.
1757 *
1758 * @return the text
1759 * @exception IllegalArgumentException if the selection doesn't
1760 * have a valid mapping into the document for some reason
1761 * @see #setText
1762 */
1763 public String getSelectedText() {
1764 String txt = null;
1765 int p0 = Math.min(caret.getDot(), caret.getMark());
1766 int p1 = Math.max(caret.getDot(), caret.getMark());
1767 if (p0 != p1) {
1768 try {
1769 Document doc = getDocument();
1770 txt = doc.getText(p0, p1 - p0);
1771 } catch (BadLocationException e) {
1772 throw new IllegalArgumentException(e.getMessage());
1773 }
1774 }
1775 return txt;
1776 }
1777
1778 /**
1779 * Returns the boolean indicating whether this
1780 * <code>TextComponent</code> is editable or not.
1781 *
1782 * @return the boolean value
1783 * @see #setEditable
1784 */
1785 public boolean isEditable() {
1786 return editable;
1787 }
1788
1789 /**
1790 * Sets the specified boolean to indicate whether or not this
1791 * <code>TextComponent</code> should be editable.
1792 * A PropertyChange event ("editable") is fired when the
1793 * state is changed.
1794 *
1795 * @param b the boolean to be set
1796 * @see #isEditable
1797 * @beaninfo
1798 * description: specifies if the text can be edited
1799 * bound: true
1800 */
1801 public void setEditable(boolean b) {
1802 if (b != editable) {
1803 boolean oldVal = editable;
1804 editable = b;
1805 enableInputMethods(editable);
1806 firePropertyChange("editable", Boolean.valueOf(oldVal), Boolean.valueOf(editable));
1807 repaint();
1808 }
1809 }
1810
1811 /**
1812 * Returns the selected text's start position. Return 0 for an
1813 * empty document, or the value of dot if no selection.
1814 *
1815 * @return the start position >= 0
1816 */
1817 @Transient
1818 public int getSelectionStart() {
1819 int start = Math.min(caret.getDot(), caret.getMark());
1820 return start;
1821 }
1822
1823 /**
1824 * Sets the selection start to the specified position. The new
1825 * starting point is constrained to be before or at the current
1826 * selection end.
1827 * <p>
1828 * This is available for backward compatibility to code
1829 * that called this method on <code>java.awt.TextComponent</code>.
1830 * This is implemented to forward to the <code>Caret</code>
1831 * implementation which is where the actual selection is maintained.
1832 *
1833 * @param selectionStart the start position of the text >= 0
1834 * @beaninfo
1835 * description: starting location of the selection.
1836 */
1837 public void setSelectionStart(int selectionStart) {
1838 /* Route through select method to enforce consistent policy
1839 * between selectionStart and selectionEnd.
1840 */
1841 select(selectionStart, getSelectionEnd());
1842 }
1843
1844 /**
1845 * Returns the selected text's end position. Return 0 if the document
1846 * is empty, or the value of dot if there is no selection.
1847 *
1848 * @return the end position >= 0
1849 */
1850 @Transient
1851 public int getSelectionEnd() {
1852 int end = Math.max(caret.getDot(), caret.getMark());
1853 return end;
1854 }
1855
1856 /**
1857 * Sets the selection end to the specified position. The new
1858 * end point is constrained to be at or after the current
1859 * selection start.
1860 * <p>
1861 * This is available for backward compatibility to code
1862 * that called this method on <code>java.awt.TextComponent</code>.
1863 * This is implemented to forward to the <code>Caret</code>
1864 * implementation which is where the actual selection is maintained.
1865 *
1866 * @param selectionEnd the end position of the text >= 0
1867 * @beaninfo
1868 * description: ending location of the selection.
1869 */
1870 public void setSelectionEnd(int selectionEnd) {
1871 /* Route through select method to enforce consistent policy
1872 * between selectionStart and selectionEnd.
1873 */
1874 select(getSelectionStart(), selectionEnd);
1875 }
1876
1877 /**
1878 * Selects the text between the specified start and end positions.
1879 * <p>
1880 * This method sets the start and end positions of the
1881 * selected text, enforcing the restriction that the start position
1882 * must be greater than or equal to zero. The end position must be
1883 * greater than or equal to the start position, and less than or
1884 * equal to the length of the text component's text.
1885 * <p>
1886 * If the caller supplies values that are inconsistent or out of
1887 * bounds, the method enforces these constraints silently, and
1888 * without failure. Specifically, if the start position or end
1889 * position is greater than the length of the text, it is reset to
1890 * equal the text length. If the start position is less than zero,
1891 * it is reset to zero, and if the end position is less than the
1892 * start position, it is reset to the start position.
1893 * <p>
1894 * This call is provided for backward compatibility.
1895 * It is routed to a call to <code>setCaretPosition</code>
1896 * followed by a call to <code>moveCaretPosition</code>.
1897 * The preferred way to manage selection is by calling
1898 * those methods directly.
1899 *
1900 * @param selectionStart the start position of the text
1901 * @param selectionEnd the end position of the text
1902 * @see #setCaretPosition
1903 * @see #moveCaretPosition
1904 */
1905 public void select(int selectionStart, int selectionEnd) {
1906 // argument adjustment done by java.awt.TextComponent
1907 int docLength = getDocument().getLength();
1908
1909 if (selectionStart < 0) {
1910 selectionStart = 0;
1911 }
1912 if (selectionStart > docLength) {
1913 selectionStart = docLength;
1914 }
1915 if (selectionEnd > docLength) {
1916 selectionEnd = docLength;
1917 }
1918 if (selectionEnd < selectionStart) {
1919 selectionEnd = selectionStart;
1920 }
1921
1922 setCaretPosition(selectionStart);
1923 moveCaretPosition(selectionEnd);
1924 }
1925
1926 /**
1927 * Selects all the text in the <code>TextComponent</code>.
1928 * Does nothing on a <code>null</code> or empty document.
1929 */
1930 public void selectAll() {
1931 Document doc = getDocument();
1932 if (doc != null) {
1933 setCaretPosition(0);
1934 moveCaretPosition(doc.getLength());
1935 }
1936 }
1937
1938 // --- Tooltip Methods ---------------------------------------------
1939
1940 /**
1941 * Returns the string to be used as the tooltip for <code>event</code>.
1942 * This will return one of:
1943 * <ol>
1944 * <li>If <code>setToolTipText</code> has been invoked with a
1945 * non-<code>null</code>
1946 * value, it will be returned, otherwise
1947 * <li>The value from invoking <code>getToolTipText</code> on
1948 * the UI will be returned.
1949 * </ol>
1950 * By default <code>JTextComponent</code> does not register
1951 * itself with the <code>ToolTipManager</code>.
1952 * This means that tooltips will NOT be shown from the
1953 * <code>TextUI</code> unless <code>registerComponent</code> has
1954 * been invoked on the <code>ToolTipManager</code>.
1955 *
1956 * @param event the event in question
1957 * @return the string to be used as the tooltip for <code>event</code>
1958 * @see javax.swing.JComponent#setToolTipText
1959 * @see javax.swing.plaf.TextUI#getToolTipText
1960 * @see javax.swing.ToolTipManager#registerComponent
1961 */
1962 public String getToolTipText(MouseEvent event) {
1963 String retValue = super.getToolTipText(event);
1964
1965 if (retValue == null) {
1966 TextUI ui = getUI();
1967 if (ui != null) {
1968 retValue = ui.getToolTipText(this, new Point(event.getX(),
1969 event.getY()));
1970 }
1971 }
1972 return retValue;
1973 }
1974
1975 // --- Scrollable methods ---------------------------------------------
1976
1977 /**
1978 * Returns the preferred size of the viewport for a view component.
1979 * This is implemented to do the default behavior of returning
1980 * the preferred size of the component.
1981 *
1982 * @return the <code>preferredSize</code> of a <code>JViewport</code>
1983 * whose view is this <code>Scrollable</code>
1984 */
1985 public Dimension getPreferredScrollableViewportSize() {
1986 return getPreferredSize();
1987 }
1988
1989
1990 /**
1991 * Components that display logical rows or columns should compute
1992 * the scroll increment that will completely expose one new row
1993 * or column, depending on the value of orientation. Ideally,
1994 * components should handle a partially exposed row or column by
1995 * returning the distance required to completely expose the item.
1996 * <p>
1997 * The default implementation of this is to simply return 10% of
1998 * the visible area. Subclasses are likely to be able to provide
1999 * a much more reasonable value.
2000 *
2001 * @param visibleRect the view area visible within the viewport
2002 * @param orientation either <code>SwingConstants.VERTICAL</code> or
2003 * <code>SwingConstants.HORIZONTAL</code>
2004 * @param direction less than zero to scroll up/left, greater than
2005 * zero for down/right
2006 * @return the "unit" increment for scrolling in the specified direction
2007 * @exception IllegalArgumentException for an invalid orientation
2008 * @see JScrollBar#setUnitIncrement
2009 */
2010 public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
2011 switch(orientation) {
2012 case SwingConstants.VERTICAL:
2013 return visibleRect.height / 10;
2014 case SwingConstants.HORIZONTAL:
2015 return visibleRect.width / 10;
2016 default:
2017 throw new IllegalArgumentException("Invalid orientation: " + orientation);
2018 }
2019 }
2020
2021
2022 /**
2023 * Components that display logical rows or columns should compute
2024 * the scroll increment that will completely expose one block
2025 * of rows or columns, depending on the value of orientation.
2026 * <p>
2027 * The default implementation of this is to simply return the visible
2028 * area. Subclasses will likely be able to provide a much more
2029 * reasonable value.
2030 *
2031 * @param visibleRect the view area visible within the viewport
2032 * @param orientation either <code>SwingConstants.VERTICAL</code> or
2033 * <code>SwingConstants.HORIZONTAL</code>
2034 * @param direction less than zero to scroll up/left, greater than zero
2035 * for down/right
2036 * @return the "block" increment for scrolling in the specified direction
2037 * @exception IllegalArgumentException for an invalid orientation
2038 * @see JScrollBar#setBlockIncrement
2039 */
2040 public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
2041 switch(orientation) {
2042 case SwingConstants.VERTICAL:
2043 return visibleRect.height;
2044 case SwingConstants.HORIZONTAL:
2045 return visibleRect.width;
2046 default:
2047 throw new IllegalArgumentException("Invalid orientation: " + orientation);
2048 }
2049 }
2050
2051
2052 /**
2053 * Returns true if a viewport should always force the width of this
2054 * <code>Scrollable</code> to match the width of the viewport.
2055 * For example a normal text view that supported line wrapping
2056 * would return true here, since it would be undesirable for
2057 * wrapped lines to disappear beyond the right
2058 * edge of the viewport. Note that returning true for a
2059 * <code>Scrollable</code> whose ancestor is a <code>JScrollPane</code>
2060 * effectively disables horizontal scrolling.
2061 * <p>
2062 * Scrolling containers, like <code>JViewport</code>,
2063 * will use this method each time they are validated.
2064 *
2065 * @return true if a viewport should force the <code>Scrollable</code>s
2066 * width to match its own
2067 */
2068 public boolean getScrollableTracksViewportWidth() {
2069 Container parent = SwingUtilities.getUnwrappedParent(this);
2070 if (parent instanceof JViewport) {
2071 return parent.getWidth() > getPreferredSize().width;
2072 }
2073 return false;
2074 }
2075
2076 /**
2077 * Returns true if a viewport should always force the height of this
2078 * <code>Scrollable</code> to match the height of the viewport.
2079 * For example a columnar text view that flowed text in left to
2080 * right columns could effectively disable vertical scrolling by
2081 * returning true here.
2082 * <p>
2083 * Scrolling containers, like <code>JViewport</code>,
2084 * will use this method each time they are validated.
2085 *
2086 * @return true if a viewport should force the Scrollables height
2087 * to match its own
2088 */
2089 public boolean getScrollableTracksViewportHeight() {
2090 Container parent = SwingUtilities.getUnwrappedParent(this);
2091 if (parent instanceof JViewport) {
2092 return parent.getHeight() > getPreferredSize().height;
2093 }
2094 return false;
2095 }
2096
2097
2098 //////////////////
2099 // Printing Support
2100 //////////////////
2101
2102 /**
2103 * A convenience print method that displays a print dialog, and then
2104 * prints this {@code JTextComponent} in <i>interactive</i> mode with no
2105 * header or footer text. Note: this method
2106 * blocks until printing is done.
2107 * <p>
2108 * Note: In <i>headless</i> mode, no dialogs will be shown.
2109 *
2110 * <p> This method calls the full featured
2111 * {@link #print(MessageFormat, MessageFormat, boolean, PrintService, PrintRequestAttributeSet, boolean)
2112 * print} method to perform printing.
2113 * @return {@code true}, unless printing is canceled by the user
2114 * @throws PrinterException if an error in the print system causes the job
2115 * to be aborted
2116 * @throws SecurityException if this thread is not allowed to
2117 * initiate a print job request
2118 *
2119 * @see #print(MessageFormat, MessageFormat, boolean, PrintService, PrintRequestAttributeSet, boolean)
2120 *
2121 * @since 1.6
2122 */
2123
2124 public boolean print() throws PrinterException {
2125 return print(null, null, true, null, null, true);
2126 }
2127
2128 /**
2129 * A convenience print method that displays a print dialog, and then
2130 * prints this {@code JTextComponent} in <i>interactive</i> mode with
2131 * the specified header and footer text. Note: this method
2132 * blocks until printing is done.
2133 * <p>
2134 * Note: In <i>headless</i> mode, no dialogs will be shown.
2135 *
2136 * <p> This method calls the full featured
2137 * {@link #print(MessageFormat, MessageFormat, boolean, PrintService, PrintRequestAttributeSet, boolean)
2138 * print} method to perform printing.
2139 * @param headerFormat the text, in {@code MessageFormat}, to be
2140 * used as the header, or {@code null} for no header
2141 * @param footerFormat the text, in {@code MessageFormat}, to be
2142 * used as the footer, or {@code null} for no footer
2143 * @return {@code true}, unless printing is canceled by the user
2144 * @throws PrinterException if an error in the print system causes the job
2145 * to be aborted
2146 * @throws SecurityException if this thread is not allowed to
2147 * initiate a print job request
2148 *
2149 * @see #print(MessageFormat, MessageFormat, boolean, PrintService, PrintRequestAttributeSet, boolean)
2150 * @see java.text.MessageFormat
2151 * @since 1.6
2152 */
2153 public boolean print(final MessageFormat headerFormat,
2154 final MessageFormat footerFormat) throws PrinterException {
2155 return print(headerFormat, footerFormat, true, null, null, true);
2156 }
2157
2158 /**
2159 * Prints the content of this {@code JTextComponent}. Note: this method
2160 * blocks until printing is done.
2161 *
2162 * <p>
2163 * Page header and footer text can be added to the output by providing
2164 * {@code MessageFormat} arguments. The printing code requests
2165 * {@code Strings} from the formats, providing a single item which may be
2166 * included in the formatted string: an {@code Integer} representing the
2167 * current page number.
2168 *
2169 * <p>
2170 * {@code showPrintDialog boolean} parameter allows you to specify whether
2171 * a print dialog is displayed to the user. When it is, the user
2172 * may use the dialog to change printing attributes or even cancel the
2173 * print.
2174 *
2175 * <p>
2176 * {@code service} allows you to provide the initial
2177 * {@code PrintService} for the print dialog, or to specify
2178 * {@code PrintService} to print to when the dialog is not shown.
2179 *
2180 * <p>
2181 * {@code attributes} can be used to provide the
2182 * initial values for the print dialog, or to supply any needed
2183 * attributes when the dialog is not shown. {@code attributes} can
2184 * be used to control how the job will print, for example
2185 * <i>duplex</i> or <i>single-sided</i>.
2186 *
2187 * <p>
2188 * {@code interactive boolean} parameter allows you to specify
2189 * whether to perform printing in <i>interactive</i>
2190 * mode. If {@code true}, a progress dialog, with an abort option,
2191 * is displayed for the duration of printing. This dialog is
2192 * <i>modal</i> when {@code print} is invoked on the <i>Event Dispatch
2193 * Thread</i> and <i>non-modal</i> otherwise. <b>Warning</b>:
2194 * calling this method on the <i>Event Dispatch Thread</i> with {@code
2195 * interactive false} blocks <i>all</i> events, including repaints, from
2196 * being processed until printing is complete. It is only
2197 * recommended when printing from an application with no
2198 * visible GUI.
2199 *
2200 * <p>
2201 * Note: In <i>headless</i> mode, {@code showPrintDialog} and
2202 * {@code interactive} parameters are ignored and no dialogs are
2203 * shown.
2204 *
2205 * <p>
2206 * This method ensures the {@code document} is not mutated during printing.
2207 * To indicate it visually, {@code setEnabled(false)} is set for the
2208 * duration of printing.
2209 *
2210 * <p>
2211 * This method uses {@link #getPrintable} to render document content.
2212 *
2213 * <p>
2214 * This method is thread-safe, although most Swing methods are not. Please
2215 * see <A
2216 * HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">
2217 * How to Use Threads</A> for more information.
2218 *
2219 * <p>
2220 * <b>Sample Usage</b>. This code snippet shows a cross-platform print
2221 * dialog and then prints the {@code JTextComponent} in <i>interactive</i> mode
2222 * unless the user cancels the dialog:
2223 *
2224 * <pre>
2225 * textComponent.print(new MessageFormat("My text component header"),
2226 * new MessageFormat("Footer. Page - {0}"), true, null, null, true);
2227 * </pre>
2228 * <p>
2229 * Executing this code off the <i>Event Dispatch Thread</i>
2230 * performs printing on the <i>background</i>.
2231 * The following pattern might be used for <i>background</i>
2232 * printing:
2233 * <pre>
2234 * FutureTask<Boolean> future =
2235 * new FutureTask<Boolean>(
2236 * new Callable<Boolean>() {
2237 * public Boolean call() {
2238 * return textComponent.print(.....);
2239 * }
2240 * });
2241 * executor.execute(future);
2242 * </pre>
2243 *
2244 * @param headerFormat the text, in {@code MessageFormat}, to be
2245 * used as the header, or {@code null} for no header
2246 * @param footerFormat the text, in {@code MessageFormat}, to be
2247 * used as the footer, or {@code null} for no footer
2248 * @param showPrintDialog {@code true} to display a print dialog,
2249 * {@code false} otherwise
2250 * @param service initial {@code PrintService}, or {@code null} for the
2251 * default
2252 * @param attributes the job attributes to be applied to the print job, or
2253 * {@code null} for none
2254 * @param interactive whether to print in an interactive mode
2255 * @return {@code true}, unless printing is canceled by the user
2256 * @throws PrinterException if an error in the print system causes the job
2257 * to be aborted
2258 * @throws SecurityException if this thread is not allowed to
2259 * initiate a print job request
2260 *
2261 * @see #getPrintable
2262 * @see java.text.MessageFormat
2263 * @see java.awt.GraphicsEnvironment#isHeadless
2264 * @see java.util.concurrent.FutureTask
2265 *
2266 * @since 1.6
2267 */
2268 public boolean print(final MessageFormat headerFormat,
2269 final MessageFormat footerFormat,
2270 final boolean showPrintDialog,
2271 final PrintService service,
2272 final PrintRequestAttributeSet attributes,
2273 final boolean interactive)
2274 throws PrinterException {
2275
2276 final PrinterJob job = PrinterJob.getPrinterJob();
2277 final Printable printable;
2278 final PrintingStatus printingStatus;
2279 final boolean isHeadless = GraphicsEnvironment.isHeadless();
2280 final boolean isEventDispatchThread =
2281 SwingUtilities.isEventDispatchThread();
2282 final Printable textPrintable = getPrintable(headerFormat, footerFormat);
2283 if (interactive && ! isHeadless) {
2284 printingStatus =
2285 PrintingStatus.createPrintingStatus(this, job);
2286 printable =
2287 printingStatus.createNotificationPrintable(textPrintable);
2288 } else {
2289 printingStatus = null;
2290 printable = textPrintable;
2291 }
2292
2293 if (service != null) {
2294 job.setPrintService(service);
2295 }
2296
2297 job.setPrintable(printable);
2298
2299 final PrintRequestAttributeSet attr = (attributes == null)
2300 ? new HashPrintRequestAttributeSet()
2301 : attributes;
2302
2303 if (showPrintDialog && ! isHeadless && ! job.printDialog(attr)) {
2304 return false;
2305 }
2306
2307 /*
2308 * there are three cases for printing:
2309 * 1. print non interactively (! interactive || isHeadless)
2310 * 2. print interactively off EDT
2311 * 3. print interactively on EDT
2312 *
2313 * 1 and 2 prints on the current thread (3 prints on another thread)
2314 * 2 and 3 deal with PrintingStatusDialog
2315 */
2316 final Callable<Object> doPrint =
2317 new Callable<Object>() {
2318 public Object call() throws Exception {
2319 try {
2320 job.print(attr);
2321 } finally {
2322 if (printingStatus != null) {
2323 printingStatus.dispose();
2324 }
2325 }
2326 return null;
2327 }
2328 };
2329
2330 final FutureTask<Object> futurePrinting =
2331 new FutureTask<Object>(doPrint);
2332
2333 final Runnable runnablePrinting =
2334 new Runnable() {
2335 public void run() {
2336 //disable component
2337 boolean wasEnabled = false;
2338 if (isEventDispatchThread) {
2339 if (isEnabled()) {
2340 wasEnabled = true;
2341 setEnabled(false);
2342 }
2343 } else {
2344 try {
2345 wasEnabled = SwingUtilities2.submit(
2346 new Callable<Boolean>() {
2347 public Boolean call() throws Exception {
2348 boolean rv = isEnabled();
2349 if (rv) {
2350 setEnabled(false);
2351 }
2352 return rv;
2353 }
2354 }).get();
2355 } catch (InterruptedException e) {
2356 throw new RuntimeException(e);
2357 } catch (ExecutionException e) {
2358 Throwable cause = e.getCause();
2359 if (cause instanceof Error) {
2360 throw (Error) cause;
2361 }
2362 if (cause instanceof RuntimeException) {
2363 throw (RuntimeException) cause;
2364 }
2365 throw new AssertionError(cause);
2366 }
2367 }
2368
2369 getDocument().render(futurePrinting);
2370
2371 //enable component
2372 if (wasEnabled) {
2373 if (isEventDispatchThread) {
2374 setEnabled(true);
2375 } else {
2376 try {
2377 SwingUtilities2.submit(
2378 new Runnable() {
2379 public void run() {
2380 setEnabled(true);
2381 }
2382 }, null).get();
2383 } catch (InterruptedException e) {
2384 throw new RuntimeException(e);
2385 } catch (ExecutionException e) {
2386 Throwable cause = e.getCause();
2387 if (cause instanceof Error) {
2388 throw (Error) cause;
2389 }
2390 if (cause instanceof RuntimeException) {
2391 throw (RuntimeException) cause;
2392 }
2393 throw new AssertionError(cause);
2394 }
2395 }
2396 }
2397 }
2398 };
2399
2400 if (! interactive || isHeadless) {
2401 runnablePrinting.run();
2402 } else {
2403 if (isEventDispatchThread) {
2404 (new Thread(runnablePrinting)).start();
2405 printingStatus.showModal(true);
2406 } else {
2407 printingStatus.showModal(false);
2408 runnablePrinting.run();
2409 }
2410 }
2411
2412 //the printing is done successfully or otherwise.
2413 //dialog is hidden if needed.
2414 try {
2415 futurePrinting.get();
2416 } catch (InterruptedException e) {
2417 throw new RuntimeException(e);
2418 } catch (ExecutionException e) {
2419 Throwable cause = e.getCause();
2420 if (cause instanceof PrinterAbortException) {
2421 if (printingStatus != null
2422 && printingStatus.isAborted()) {
2423 return false;
2424 } else {
2425 throw (PrinterAbortException) cause;
2426 }
2427 } else if (cause instanceof PrinterException) {
2428 throw (PrinterException) cause;
2429 } else if (cause instanceof RuntimeException) {
2430 throw (RuntimeException) cause;
2431 } else if (cause instanceof Error) {
2432 throw (Error) cause;
2433 } else {
2434 throw new AssertionError(cause);
2435 }
2436 }
2437 return true;
2438 }
2439
2440
2441 /**
2442 * Returns a {@code Printable} to use for printing the content of this
2443 * {@code JTextComponent}. The returned {@code Printable} prints
2444 * the document as it looks on the screen except being reformatted
2445 * to fit the paper.
2446 * The returned {@code Printable} can be wrapped inside another
2447 * {@code Printable} in order to create complex reports and
2448 * documents.
2449 *
2450 *
2451 * <p>
2452 * The returned {@code Printable} shares the {@code document} with this
2453 * {@code JTextComponent}. It is the responsibility of the developer to
2454 * ensure that the {@code document} is not mutated while this {@code Printable}
2455 * is used. Printing behavior is undefined when the {@code document} is
2456 * mutated during printing.
2457 *
2458 * <p>
2459 * Page header and footer text can be added to the output by providing
2460 * {@code MessageFormat} arguments. The printing code requests
2461 * {@code Strings} from the formats, providing a single item which may be
2462 * included in the formatted string: an {@code Integer} representing the
2463 * current page number.
2464 *
2465 * <p>
2466 * The returned {@code Printable} when printed, formats the
2467 * document content appropriately for the page size. For correct
2468 * line wrapping the {@code imageable width} of all pages must be the
2469 * same. See {@link java.awt.print.PageFormat#getImageableWidth}.
2470 *
2471 * <p>
2472 * This method is thread-safe, although most Swing methods are not. Please
2473 * see <A
2474 * HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">
2475 * How to Use Threads</A> for more information.
2476 *
2477 * <p>
2478 * The returned {@code Printable} can be printed on any thread.
2479 *
2480 * <p>
2481 * This implementation returned {@code Printable} performs all painting on
2482 * the <i>Event Dispatch Thread</i>, regardless of what thread it is
2483 * used on.
2484 *
2485 * @param headerFormat the text, in {@code MessageFormat}, to be
2486 * used as the header, or {@code null} for no header
2487 * @param footerFormat the text, in {@code MessageFormat}, to be
2488 * used as the footer, or {@code null} for no footer
2489 * @return a {@code Printable} for use in printing content of this
2490 * {@code JTextComponent}
2491 *
2492 *
2493 * @see java.awt.print.Printable
2494 * @see java.awt.print.PageFormat
2495 * @see javax.swing.text.Document#render(java.lang.Runnable)
2496 *
2497 * @since 1.6
2498 */
2499 public Printable getPrintable(final MessageFormat headerFormat,
2500 final MessageFormat footerFormat) {
2501 return TextComponentPrintable.getPrintable(
2502 this, headerFormat, footerFormat);
2503 }
2504
2505
2506 /////////////////
2507 // Accessibility support
2508 ////////////////
2509
2510
2511 /**
2512 * Gets the <code>AccessibleContext</code> associated with this
2513 * <code>JTextComponent</code>. For text components,
2514 * the <code>AccessibleContext</code> takes the form of an
2515 * <code>AccessibleJTextComponent</code>.
2516 * A new <code>AccessibleJTextComponent</code> instance
2517 * is created if necessary.
2518 *
2519 * @return an <code>AccessibleJTextComponent</code> that serves as the
2520 * <code>AccessibleContext</code> of this
2521 * <code>JTextComponent</code>
2522 */
2523 public AccessibleContext getAccessibleContext() {
2524 if (accessibleContext == null) {
2525 accessibleContext = new AccessibleJTextComponent();
2526 }
2527 return accessibleContext;
2528 }
2529
2530 /**
2531 * This class implements accessibility support for the
2532 * <code>JTextComponent</code> class. It provides an implementation of
2533 * the Java Accessibility API appropriate to menu user-interface elements.
2534 * <p>
2535 * <strong>Warning:</strong>
2536 * Serialized objects of this class will not be compatible with
2537 * future Swing releases. The current serialization support is
2538 * appropriate for short term storage or RMI between applications running
2539 * the same version of Swing. As of 1.4, support for long term storage
2540 * of all JavaBeans<sup><font size="-2">TM</font></sup>
2541 * has been added to the <code>java.beans</code> package.
2542 * Please see {@link java.beans.XMLEncoder}.
2543 */
2544 public class AccessibleJTextComponent extends AccessibleJComponent
2545 implements AccessibleText, CaretListener, DocumentListener,
2546 AccessibleAction, AccessibleEditableText,
2547 AccessibleExtendedText {
2548
2549 int caretPos;
2550 Point oldLocationOnScreen;
2551
2552 /**
2553 * Constructs an AccessibleJTextComponent. Adds a listener to track
2554 * caret change.
2555 */
2556 public AccessibleJTextComponent() {
2557 Document doc = JTextComponent.this.getDocument();
2558 if (doc != null) {
2559 doc.addDocumentListener(this);
2560 }
2561 JTextComponent.this.addCaretListener(this);
2562 caretPos = getCaretPosition();
2563
2564 try {
2565 oldLocationOnScreen = getLocationOnScreen();
2566 } catch (IllegalComponentStateException iae) {
2567 }
2568
2569 // Fire a ACCESSIBLE_VISIBLE_DATA_PROPERTY PropertyChangeEvent
2570 // when the text component moves (e.g., when scrolling).
2571 // Using an anonymous class since making AccessibleJTextComponent
2572 // implement ComponentListener would be an API change.
2573 JTextComponent.this.addComponentListener(new ComponentAdapter() {
2574
2575 public void componentMoved(ComponentEvent e) {
2576 try {
2577 Point newLocationOnScreen = getLocationOnScreen();
2578 firePropertyChange(ACCESSIBLE_VISIBLE_DATA_PROPERTY,
2579 oldLocationOnScreen,
2580 newLocationOnScreen);
2581
2582 oldLocationOnScreen = newLocationOnScreen;
2583 } catch (IllegalComponentStateException iae) {
2584 }
2585 }
2586 });
2587 }
2588
2589 /**
2590 * Handles caret updates (fire appropriate property change event,
2591 * which are AccessibleContext.ACCESSIBLE_CARET_PROPERTY and
2592 * AccessibleContext.ACCESSIBLE_SELECTION_PROPERTY).
2593 * This keeps track of the dot position internally. When the caret
2594 * moves, the internal position is updated after firing the event.
2595 *
2596 * @param e the CaretEvent
2597 */
2598 public void caretUpdate(CaretEvent e) {
2599 int dot = e.getDot();
2600 int mark = e.getMark();
2601 if (caretPos != dot) {
2602 // the caret moved
2603 firePropertyChange(ACCESSIBLE_CARET_PROPERTY,
2604 new Integer(caretPos), new Integer(dot));
2605 caretPos = dot;
2606
2607 try {
2608 oldLocationOnScreen = getLocationOnScreen();
2609 } catch (IllegalComponentStateException iae) {
2610 }
2611 }
2612 if (mark != dot) {
2613 // there is a selection
2614 firePropertyChange(ACCESSIBLE_SELECTION_PROPERTY, null,
2615 getSelectedText());
2616 }
2617 }
2618
2619 // DocumentListener methods
2620
2621 /**
2622 * Handles document insert (fire appropriate property change event
2623 * which is AccessibleContext.ACCESSIBLE_TEXT_PROPERTY).
2624 * This tracks the changed offset via the event.
2625 *
2626 * @param e the DocumentEvent
2627 */
2628 public void insertUpdate(DocumentEvent e) {
2629 final Integer pos = new Integer (e.getOffset());
2630 if (SwingUtilities.isEventDispatchThread()) {
2631 firePropertyChange(ACCESSIBLE_TEXT_PROPERTY, null, pos);
2632 } else {
2633 Runnable doFire = new Runnable() {
2634 public void run() {
2635 firePropertyChange(ACCESSIBLE_TEXT_PROPERTY,
2636 null, pos);
2637 }
2638 };
2639 SwingUtilities.invokeLater(doFire);
2640 }
2641 }
2642
2643 /**
2644 * Handles document remove (fire appropriate property change event,
2645 * which is AccessibleContext.ACCESSIBLE_TEXT_PROPERTY).
2646 * This tracks the changed offset via the event.
2647 *
2648 * @param e the DocumentEvent
2649 */
2650 public void removeUpdate(DocumentEvent e) {
2651 final Integer pos = new Integer (e.getOffset());
2652 if (SwingUtilities.isEventDispatchThread()) {
2653 firePropertyChange(ACCESSIBLE_TEXT_PROPERTY, null, pos);
2654 } else {
2655 Runnable doFire = new Runnable() {
2656 public void run() {
2657 firePropertyChange(ACCESSIBLE_TEXT_PROPERTY,
2658 null, pos);
2659 }
2660 };
2661 SwingUtilities.invokeLater(doFire);
2662 }
2663 }
2664
2665 /**
2666 * Handles document remove (fire appropriate property change event,
2667 * which is AccessibleContext.ACCESSIBLE_TEXT_PROPERTY).
2668 * This tracks the changed offset via the event.
2669 *
2670 * @param e the DocumentEvent
2671 */
2672 public void changedUpdate(DocumentEvent e) {
2673 final Integer pos = new Integer (e.getOffset());
2674 if (SwingUtilities.isEventDispatchThread()) {
2675 firePropertyChange(ACCESSIBLE_TEXT_PROPERTY, null, pos);
2676 } else {
2677 Runnable doFire = new Runnable() {
2678 public void run() {
2679 firePropertyChange(ACCESSIBLE_TEXT_PROPERTY,
2680 null, pos);
2681 }
2682 };
2683 SwingUtilities.invokeLater(doFire);
2684 }
2685 }
2686
2687 /**
2688 * Gets the state set of the JTextComponent.
2689 * The AccessibleStateSet of an object is composed of a set of
2690 * unique AccessibleState's. A change in the AccessibleStateSet
2691 * of an object will cause a PropertyChangeEvent to be fired
2692 * for the AccessibleContext.ACCESSIBLE_STATE_PROPERTY property.
2693 *
2694 * @return an instance of AccessibleStateSet containing the
2695 * current state set of the object
2696 * @see AccessibleStateSet
2697 * @see AccessibleState
2698 * @see #addPropertyChangeListener
2699 */
2700 public AccessibleStateSet getAccessibleStateSet() {
2701 AccessibleStateSet states = super.getAccessibleStateSet();
2702 if (JTextComponent.this.isEditable()) {
2703 states.add(AccessibleState.EDITABLE);
2704 }
2705 return states;
2706 }
2707
2708
2709 /**
2710 * Gets the role of this object.
2711 *
2712 * @return an instance of AccessibleRole describing the role of the
2713 * object (AccessibleRole.TEXT)
2714 * @see AccessibleRole
2715 */
2716 public AccessibleRole getAccessibleRole() {
2717 return AccessibleRole.TEXT;
2718 }
2719
2720 /**
2721 * Get the AccessibleText associated with this object. In the
2722 * implementation of the Java Accessibility API for this class,
2723 * return this object, which is responsible for implementing the
2724 * AccessibleText interface on behalf of itself.
2725 *
2726 * @return this object
2727 */
2728 public AccessibleText getAccessibleText() {
2729 return this;
2730 }
2731
2732
2733 // --- interface AccessibleText methods ------------------------
2734
2735 /**
2736 * Many of these methods are just convenience methods; they
2737 * just call the equivalent on the parent
2738 */
2739
2740 /**
2741 * Given a point in local coordinates, return the zero-based index
2742 * of the character under that Point. If the point is invalid,
2743 * this method returns -1.
2744 *
2745 * @param p the Point in local coordinates
2746 * @return the zero-based index of the character under Point p.
2747 */
2748 public int getIndexAtPoint(Point p) {
2749 if (p == null) {
2750 return -1;
2751 }
2752 return JTextComponent.this.viewToModel(p);
2753 }
2754
2755 /**
2756 * Gets the editor's drawing rectangle. Stolen
2757 * from the unfortunately named
2758 * BasicTextUI.getVisibleEditorRect()
2759 *
2760 * @return the bounding box for the root view
2761 */
2762 Rectangle getRootEditorRect() {
2763 Rectangle alloc = JTextComponent.this.getBounds();
2764 if ((alloc.width > 0) && (alloc.height > 0)) {
2765 alloc.x = alloc.y = 0;
2766 Insets insets = JTextComponent.this.getInsets();
2767 alloc.x += insets.left;
2768 alloc.y += insets.top;
2769 alloc.width -= insets.left + insets.right;
2770 alloc.height -= insets.top + insets.bottom;
2771 return alloc;
2772 }
2773 return null;
2774 }
2775
2776 /**
2777 * Determines the bounding box of the character at the given
2778 * index into the string. The bounds are returned in local
2779 * coordinates. If the index is invalid a null rectangle
2780 * is returned.
2781 *
2782 * The screen coordinates returned are "unscrolled coordinates"
2783 * if the JTextComponent is contained in a JScrollPane in which
2784 * case the resulting rectangle should be composed with the parent
2785 * coordinates. A good algorithm to use is:
2786 * <nf>
2787 * Accessible a:
2788 * AccessibleText at = a.getAccessibleText();
2789 * AccessibleComponent ac = a.getAccessibleComponent();
2790 * Rectangle r = at.getCharacterBounds();
2791 * Point p = ac.getLocation();
2792 * r.x += p.x;
2793 * r.y += p.y;
2794 * </nf>
2795 *
2796 * Note: the JTextComponent must have a valid size (e.g. have
2797 * been added to a parent container whose ancestor container
2798 * is a valid top-level window) for this method to be able
2799 * to return a meaningful (non-null) value.
2800 *
2801 * @param i the index into the String >= 0
2802 * @return the screen coordinates of the character's bounding box
2803 */
2804 public Rectangle getCharacterBounds(int i) {
2805 if (i < 0 || i > model.getLength()-1) {
2806 return null;
2807 }
2808 TextUI ui = getUI();
2809 if (ui == null) {
2810 return null;
2811 }
2812 Rectangle rect = null;
2813 Rectangle alloc = getRootEditorRect();
2814 if (alloc == null) {
2815 return null;
2816 }
2817 if (model instanceof AbstractDocument) {
2818 ((AbstractDocument)model).readLock();
2819 }
2820 try {
2821 View rootView = ui.getRootView(JTextComponent.this);
2822 if (rootView != null) {
2823 rootView.setSize(alloc.width, alloc.height);
2824
2825 Shape bounds = rootView.modelToView(i,
2826 Position.Bias.Forward, i+1,
2827 Position.Bias.Backward, alloc);
2828
2829 rect = (bounds instanceof Rectangle) ?
2830 (Rectangle)bounds : bounds.getBounds();
2831
2832 }
2833 } catch (BadLocationException e) {
2834 } finally {
2835 if (model instanceof AbstractDocument) {
2836 ((AbstractDocument)model).readUnlock();
2837 }
2838 }
2839 return rect;
2840 }
2841
2842 /**
2843 * Returns the number of characters (valid indices)
2844 *
2845 * @return the number of characters >= 0
2846 */
2847 public int getCharCount() {
2848 return model.getLength();
2849 }
2850
2851 /**
2852 * Returns the zero-based offset of the caret.
2853 *
2854 * Note: The character to the right of the caret will have the
2855 * same index value as the offset (the caret is between
2856 * two characters).
2857 *
2858 * @return the zero-based offset of the caret.
2859 */
2860 public int getCaretPosition() {
2861 return JTextComponent.this.getCaretPosition();
2862 }
2863
2864 /**
2865 * Returns the AttributeSet for a given character (at a given index).
2866 *
2867 * @param i the zero-based index into the text
2868 * @return the AttributeSet of the character
2869 */
2870 public AttributeSet getCharacterAttribute(int i) {
2871 Element e = null;
2872 if (model instanceof AbstractDocument) {
2873 ((AbstractDocument)model).readLock();
2874 }
2875 try {
2876 for (e = model.getDefaultRootElement(); ! e.isLeaf(); ) {
2877 int index = e.getElementIndex(i);
2878 e = e.getElement(index);
2879 }
2880 } finally {
2881 if (model instanceof AbstractDocument) {
2882 ((AbstractDocument)model).readUnlock();
2883 }
2884 }
2885 return e.getAttributes();
2886 }
2887
2888
2889 /**
2890 * Returns the start offset within the selected text.
2891 * If there is no selection, but there is
2892 * a caret, the start and end offsets will be the same.
2893 * Return 0 if the text is empty, or the caret position
2894 * if no selection.
2895 *
2896 * @return the index into the text of the start of the selection >= 0
2897 */
2898 public int getSelectionStart() {
2899 return JTextComponent.this.getSelectionStart();
2900 }
2901
2902 /**
2903 * Returns the end offset within the selected text.
2904 * If there is no selection, but there is
2905 * a caret, the start and end offsets will be the same.
2906 * Return 0 if the text is empty, or the caret position
2907 * if no selection.
2908 *
2909 * @return the index into teh text of the end of the selection >= 0
2910 */
2911 public int getSelectionEnd() {
2912 return JTextComponent.this.getSelectionEnd();
2913 }
2914
2915 /**
2916 * Returns the portion of the text that is selected.
2917 *
2918 * @return the text, null if no selection
2919 */
2920 public String getSelectedText() {
2921 return JTextComponent.this.getSelectedText();
2922 }
2923
2924 /**
2925 * IndexedSegment extends Segment adding the offset into the
2926 * the model the <code>Segment</code> was asked for.
2927 */
2928 private class IndexedSegment extends Segment {
2929 /**
2930 * Offset into the model that the position represents.
2931 */
2932 public int modelOffset;
2933 }
2934
2935
2936 // TIGER - 4170173
2937 /**
2938 * Returns the String at a given index. Whitespace
2939 * between words is treated as a word.
2940 *
2941 * @param part the CHARACTER, WORD, or SENTENCE to retrieve
2942 * @param index an index within the text
2943 * @return the letter, word, or sentence.
2944 *
2945 */
2946 public String getAtIndex(int part, int index) {
2947 return getAtIndex(part, index, 0);
2948 }
2949
2950
2951 /**
2952 * Returns the String after a given index. Whitespace
2953 * between words is treated as a word.
2954 *
2955 * @param part the CHARACTER, WORD, or SENTENCE to retrieve
2956 * @param index an index within the text
2957 * @return the letter, word, or sentence.
2958 */
2959 public String getAfterIndex(int part, int index) {
2960 return getAtIndex(part, index, 1);
2961 }
2962
2963
2964 /**
2965 * Returns the String before a given index. Whitespace
2966 * between words is treated a word.
2967 *
2968 * @param part the CHARACTER, WORD, or SENTENCE to retrieve
2969 * @param index an index within the text
2970 * @return the letter, word, or sentence.
2971 */
2972 public String getBeforeIndex(int part, int index) {
2973 return getAtIndex(part, index, -1);
2974 }
2975
2976
2977 /**
2978 * Gets the word, sentence, or character at <code>index</code>.
2979 * If <code>direction</code> is non-null this will find the
2980 * next/previous word/sentence/character.
2981 */
2982 private String getAtIndex(int part, int index, int direction) {
2983 if (model instanceof AbstractDocument) {
2984 ((AbstractDocument)model).readLock();
2985 }
2986 try {
2987 if (index < 0 || index >= model.getLength()) {
2988 return null;
2989 }
2990 switch (part) {
2991 case AccessibleText.CHARACTER:
2992 if (index + direction < model.getLength() &&
2993 index + direction >= 0) {
2994 return model.getText(index + direction, 1);
2995 }
2996 break;
2997
2998
2999 case AccessibleText.WORD:
3000 case AccessibleText.SENTENCE:
3001 IndexedSegment seg = getSegmentAt(part, index);
3002 if (seg != null) {
3003 if (direction != 0) {
3004 int next;
3005
3006
3007 if (direction < 0) {
3008 next = seg.modelOffset - 1;
3009 }
3010 else {
3011 next = seg.modelOffset + direction * seg.count;
3012 }
3013 if (next >= 0 && next <= model.getLength()) {
3014 seg = getSegmentAt(part, next);
3015 }
3016 else {
3017 seg = null;
3018 }
3019 }
3020 if (seg != null) {
3021 return new String(seg.array, seg.offset,
3022 seg.count);
3023 }
3024 }
3025 break;
3026
3027
3028 default:
3029 break;
3030 }
3031 } catch (BadLocationException e) {
3032 } finally {
3033 if (model instanceof AbstractDocument) {
3034 ((AbstractDocument)model).readUnlock();
3035 }
3036 }
3037 return null;
3038 }
3039
3040
3041 /*
3042 * Returns the paragraph element for the specified index.
3043 */
3044 private Element getParagraphElement(int index) {
3045 if (model instanceof PlainDocument ) {
3046 PlainDocument sdoc = (PlainDocument)model;
3047 return sdoc.getParagraphElement(index);
3048 } else if (model instanceof StyledDocument) {
3049 StyledDocument sdoc = (StyledDocument)model;
3050 return sdoc.getParagraphElement(index);
3051 } else {
3052 Element para;
3053 for (para = model.getDefaultRootElement(); ! para.isLeaf(); ) {
3054 int pos = para.getElementIndex(index);
3055 para = para.getElement(pos);
3056 }
3057 if (para == null) {
3058 return null;
3059 }
3060 return para.getParentElement();
3061 }
3062 }
3063
3064 /*
3065 * Returns a <code>Segment</code> containing the paragraph text
3066 * at <code>index</code>, or null if <code>index</code> isn't
3067 * valid.
3068 */
3069 private IndexedSegment getParagraphElementText(int index)
3070 throws BadLocationException {
3071 Element para = getParagraphElement(index);
3072
3073
3074 if (para != null) {
3075 IndexedSegment segment = new IndexedSegment();
3076 try {
3077 int length = para.getEndOffset() - para.getStartOffset();
3078 model.getText(para.getStartOffset(), length, segment);
3079 } catch (BadLocationException e) {
3080 return null;
3081 }
3082 segment.modelOffset = para.getStartOffset();
3083 return segment;
3084 }
3085 return null;
3086 }
3087
3088
3089 /**
3090 * Returns the Segment at <code>index</code> representing either
3091 * the paragraph or sentence as identified by <code>part</code>, or
3092 * null if a valid paragraph/sentence can't be found. The offset
3093 * will point to the start of the word/sentence in the array, and
3094 * the modelOffset will point to the location of the word/sentence
3095 * in the model.
3096 */
3097 private IndexedSegment getSegmentAt(int part, int index) throws
3098 BadLocationException {
3099 IndexedSegment seg = getParagraphElementText(index);
3100 if (seg == null) {
3101 return null;
3102 }
3103 BreakIterator iterator;
3104 switch (part) {
3105 case AccessibleText.WORD:
3106 iterator = BreakIterator.getWordInstance(getLocale());
3107 break;
3108 case AccessibleText.SENTENCE:
3109 iterator = BreakIterator.getSentenceInstance(getLocale());
3110 break;
3111 default:
3112 return null;
3113 }
3114 seg.first();
3115 iterator.setText(seg);
3116 int end = iterator.following(index - seg.modelOffset + seg.offset);
3117 if (end == BreakIterator.DONE) {
3118 return null;
3119 }
3120 if (end > seg.offset + seg.count) {
3121 return null;
3122 }
3123 int begin = iterator.previous();
3124 if (begin == BreakIterator.DONE ||
3125 begin >= seg.offset + seg.count) {
3126 return null;
3127 }
3128 seg.modelOffset = seg.modelOffset + begin - seg.offset;
3129 seg.offset = begin;
3130 seg.count = end - begin;
3131 return seg;
3132 }
3133
3134 // begin AccessibleEditableText methods -----
3135
3136 /**
3137 * Returns the AccessibleEditableText interface for
3138 * this text component.
3139 *
3140 * @return the AccessibleEditableText interface
3141 * @since 1.4
3142 */
3143 public AccessibleEditableText getAccessibleEditableText() {
3144 return this;
3145 }
3146
3147 /**
3148 * Sets the text contents to the specified string.
3149 *
3150 * @param s the string to set the text contents
3151 * @since 1.4
3152 */
3153 public void setTextContents(String s) {
3154 JTextComponent.this.setText(s);
3155 }
3156
3157 /**
3158 * Inserts the specified string at the given index
3159 *
3160 * @param index the index in the text where the string will
3161 * be inserted
3162 * @param s the string to insert in the text
3163 * @since 1.4
3164 */
3165 public void insertTextAtIndex(int index, String s) {
3166 Document doc = JTextComponent.this.getDocument();
3167 if (doc != null) {
3168 try {
3169 if (s != null && s.length() > 0) {
3170 boolean composedTextSaved = saveComposedText(index);
3171 doc.insertString(index, s, null);
3172 if (composedTextSaved) {
3173 restoreComposedText();
3174 }
3175 }
3176 } catch (BadLocationException e) {
3177 UIManager.getLookAndFeel().provideErrorFeedback(JTextComponent.this);
3178 }
3179 }
3180 }
3181
3182 /**
3183 * Returns the text string between two indices.
3184 *
3185 * @param startIndex the starting index in the text
3186 * @param endIndex the ending index in the text
3187 * @return the text string between the indices
3188 * @since 1.4
3189 */
3190 public String getTextRange(int startIndex, int endIndex) {
3191 String txt = null;
3192 int p0 = Math.min(startIndex, endIndex);
3193 int p1 = Math.max(startIndex, endIndex);
3194 if (p0 != p1) {
3195 try {
3196 Document doc = JTextComponent.this.getDocument();
3197 txt = doc.getText(p0, p1 - p0);
3198 } catch (BadLocationException e) {
3199 throw new IllegalArgumentException(e.getMessage());
3200 }
3201 }
3202 return txt;
3203 }
3204
3205 /**
3206 * Deletes the text between two indices
3207 *
3208 * @param startIndex the starting index in the text
3209 * @param endIndex the ending index in the text
3210 * @since 1.4
3211 */
3212 public void delete(int startIndex, int endIndex) {
3213 if (isEditable() && isEnabled()) {
3214 try {
3215 int p0 = Math.min(startIndex, endIndex);
3216 int p1 = Math.max(startIndex, endIndex);
3217 if (p0 != p1) {
3218 Document doc = getDocument();
3219 doc.remove(p0, p1 - p0);
3220 }
3221 } catch (BadLocationException e) {
3222 }
3223 } else {
3224 UIManager.getLookAndFeel().provideErrorFeedback(JTextComponent.this);
3225 }
3226 }
3227
3228 /**
3229 * Cuts the text between two indices into the system clipboard.
3230 *
3231 * @param startIndex the starting index in the text
3232 * @param endIndex the ending index in the text
3233 * @since 1.4
3234 */
3235 public void cut(int startIndex, int endIndex) {
3236 selectText(startIndex, endIndex);
3237 JTextComponent.this.cut();
3238 }
3239
3240 /**
3241 * Pastes the text from the system clipboard into the text
3242 * starting at the specified index.
3243 *
3244 * @param startIndex the starting index in the text
3245 * @since 1.4
3246 */
3247 public void paste(int startIndex) {
3248 setCaretPosition(startIndex);
3249 JTextComponent.this.paste();
3250 }
3251
3252 /**
3253 * Replaces the text between two indices with the specified
3254 * string.
3255 *
3256 * @param startIndex the starting index in the text
3257 * @param endIndex the ending index in the text
3258 * @param s the string to replace the text between two indices
3259 * @since 1.4
3260 */
3261 public void replaceText(int startIndex, int endIndex, String s) {
3262 selectText(startIndex, endIndex);
3263 JTextComponent.this.replaceSelection(s);
3264 }
3265
3266 /**
3267 * Selects the text between two indices.
3268 *
3269 * @param startIndex the starting index in the text
3270 * @param endIndex the ending index in the text
3271 * @since 1.4
3272 */
3273 public void selectText(int startIndex, int endIndex) {
3274 JTextComponent.this.select(startIndex, endIndex);
3275 }
3276
3277 /**
3278 * Sets attributes for the text between two indices.
3279 *
3280 * @param startIndex the starting index in the text
3281 * @param endIndex the ending index in the text
3282 * @param as the attribute set
3283 * @see AttributeSet
3284 * @since 1.4
3285 */
3286 public void setAttributes(int startIndex, int endIndex,
3287 AttributeSet as) {
3288
3289 // Fixes bug 4487492
3290 Document doc = JTextComponent.this.getDocument();
3291 if (doc != null && doc instanceof StyledDocument) {
3292 StyledDocument sDoc = (StyledDocument)doc;
3293 int offset = startIndex;
3294 int length = endIndex - startIndex;
3295 sDoc.setCharacterAttributes(offset, length, as, true);
3296 }
3297 }
3298
3299 // ----- end AccessibleEditableText methods
3300
3301
3302 // ----- begin AccessibleExtendedText methods
3303
3304 // Probably should replace the helper method getAtIndex() to return
3305 // instead an AccessibleTextSequence also for LINE & ATTRIBUTE_RUN
3306 // and then make the AccessibleText methods get[At|After|Before]Point
3307 // call this new method instead and return only the string portion
3308
3309 /**
3310 * Returns the AccessibleTextSequence at a given <code>index</code>.
3311 * If <code>direction</code> is non-null this will find the
3312 * next/previous word/sentence/character.
3313 *
3314 * @param part the <code>CHARACTER</code>, <code>WORD</code>,
3315 * <code>SENTENCE</code>, <code>LINE</code> or
3316 * <code>ATTRIBUTE_RUN</code> to retrieve
3317 * @param index an index within the text
3318 * @param direction is either -1, 0, or 1
3319 * @return an <code>AccessibleTextSequence</code> specifying the text
3320 * if <code>part</code> and <code>index</code> are valid. Otherwise,
3321 * <code>null</code> is returned.
3322 *
3323 * @see javax.accessibility.AccessibleText#CHARACTER
3324 * @see javax.accessibility.AccessibleText#WORD
3325 * @see javax.accessibility.AccessibleText#SENTENCE
3326 * @see javax.accessibility.AccessibleExtendedText#LINE
3327 * @see javax.accessibility.AccessibleExtendedText#ATTRIBUTE_RUN
3328 *
3329 * @since 1.6
3330 */
3331 private AccessibleTextSequence getSequenceAtIndex(int part,
3332 int index, int direction) {
3333 if (index < 0 || index >= model.getLength()) {
3334 return null;
3335 }
3336 if (direction < -1 || direction > 1) {
3337 return null; // direction must be 1, 0, or -1
3338 }
3339
3340 switch (part) {
3341 case AccessibleText.CHARACTER:
3342 if (model instanceof AbstractDocument) {
3343 ((AbstractDocument)model).readLock();
3344 }
3345 AccessibleTextSequence charSequence = null;
3346 try {
3347 if (index + direction < model.getLength() &&
3348 index + direction >= 0) {
3349 charSequence =
3350 new AccessibleTextSequence(index + direction,
3351 index + direction + 1,
3352 model.getText(index + direction, 1));
3353 }
3354
3355 } catch (BadLocationException e) {
3356 // we are intentionally silent; our contract says we return
3357 // null if there is any failure in this method
3358 } finally {
3359 if (model instanceof AbstractDocument) {
3360 ((AbstractDocument)model).readUnlock();
3361 }
3362 }
3363 return charSequence;
3364
3365 case AccessibleText.WORD:
3366 case AccessibleText.SENTENCE:
3367 if (model instanceof AbstractDocument) {
3368 ((AbstractDocument)model).readLock();
3369 }
3370 AccessibleTextSequence rangeSequence = null;
3371 try {
3372 IndexedSegment seg = getSegmentAt(part, index);
3373 if (seg != null) {
3374 if (direction != 0) {
3375 int next;
3376
3377 if (direction < 0) {
3378 next = seg.modelOffset - 1;
3379 }
3380 else {
3381 next = seg.modelOffset + seg.count;
3382 }
3383 if (next >= 0 && next <= model.getLength()) {
3384 seg = getSegmentAt(part, next);
3385 }
3386 else {
3387 seg = null;
3388 }
3389 }
3390 if (seg != null &&
3391 (seg.offset + seg.count) <= model.getLength()) {
3392 rangeSequence =
3393 new AccessibleTextSequence (seg.offset,
3394 seg.offset + seg.count,
3395 new String(seg.array, seg.offset, seg.count));
3396 } // else we leave rangeSequence set to null
3397 }
3398 } catch(BadLocationException e) {
3399 // we are intentionally silent; our contract says we return
3400 // null if there is any failure in this method
3401 } finally {
3402 if (model instanceof AbstractDocument) {
3403 ((AbstractDocument)model).readUnlock();
3404 }
3405 }
3406 return rangeSequence;
3407
3408 case AccessibleExtendedText.LINE:
3409 AccessibleTextSequence lineSequence = null;
3410 if (model instanceof AbstractDocument) {
3411 ((AbstractDocument)model).readLock();
3412 }
3413 try {
3414 int startIndex =
3415 Utilities.getRowStart(JTextComponent.this, index);
3416 int endIndex =
3417 Utilities.getRowEnd(JTextComponent.this, index);
3418 if (startIndex >= 0 && endIndex >= startIndex) {
3419 if (direction == 0) {
3420 lineSequence =
3421 new AccessibleTextSequence(startIndex, endIndex,
3422 model.getText(startIndex,
3423 endIndex - startIndex + 1));
3424 } else if (direction == -1 && startIndex > 0) {
3425 endIndex =
3426 Utilities.getRowEnd(JTextComponent.this,
3427 startIndex - 1);
3428 startIndex =
3429 Utilities.getRowStart(JTextComponent.this,
3430 startIndex - 1);
3431 if (startIndex >= 0 && endIndex >= startIndex) {
3432 lineSequence =
3433 new AccessibleTextSequence(startIndex,
3434 endIndex,
3435 model.getText(startIndex,
3436 endIndex - startIndex + 1));
3437 }
3438 } else if (direction == 1 &&
3439 endIndex < model.getLength()) {
3440 startIndex =
3441 Utilities.getRowStart(JTextComponent.this,
3442 endIndex + 1);
3443 endIndex =
3444 Utilities.getRowEnd(JTextComponent.this,
3445 endIndex + 1);
3446 if (startIndex >= 0 && endIndex >= startIndex) {
3447 lineSequence =
3448 new AccessibleTextSequence(startIndex,
3449 endIndex, model.getText(startIndex,
3450 endIndex - startIndex + 1));
3451 }
3452 }
3453 // already validated 'direction' above...
3454 }
3455 } catch(BadLocationException e) {
3456 // we are intentionally silent; our contract says we return
3457 // null if there is any failure in this method
3458 } finally {
3459 if (model instanceof AbstractDocument) {
3460 ((AbstractDocument)model).readUnlock();
3461 }
3462 }
3463 return lineSequence;
3464
3465 case AccessibleExtendedText.ATTRIBUTE_RUN:
3466 // assumptions: (1) that all characters in a single element
3467 // share the same attribute set; (2) that adjacent elements
3468 // *may* share the same attribute set
3469
3470 int attributeRunStartIndex, attributeRunEndIndex;
3471 String runText = null;
3472 if (model instanceof AbstractDocument) {
3473 ((AbstractDocument)model).readLock();
3474 }
3475
3476 try {
3477 attributeRunStartIndex = attributeRunEndIndex =
3478 Integer.MIN_VALUE;
3479 int tempIndex = index;
3480 switch (direction) {
3481 case -1:
3482 // going backwards, so find left edge of this run -
3483 // that'll be the end of the previous run
3484 // (off-by-one counting)
3485 attributeRunEndIndex = getRunEdge(index, direction);
3486 // now set ourselves up to find the left edge of the
3487 // prev. run
3488 tempIndex = attributeRunEndIndex - 1;
3489 break;
3490 case 1:
3491 // going forward, so find right edge of this run -
3492 // that'll be the start of the next run
3493 // (off-by-one counting)
3494 attributeRunStartIndex = getRunEdge(index, direction);
3495 // now set ourselves up to find the right edge of the
3496 // next run
3497 tempIndex = attributeRunStartIndex;
3498 break;
3499 case 0:
3500 // interested in the current run, so nothing special to
3501 // set up in advance...
3502 break;
3503 default:
3504 // only those three values of direction allowed...
3505 throw new AssertionError(direction);
3506 }
3507
3508 // set the unset edge; if neither set then we're getting
3509 // both edges of the current run around our 'index'
3510 attributeRunStartIndex =
3511 (attributeRunStartIndex != Integer.MIN_VALUE) ?
3512 attributeRunStartIndex : getRunEdge(tempIndex, -1);
3513 attributeRunEndIndex =
3514 (attributeRunEndIndex != Integer.MIN_VALUE) ?
3515 attributeRunEndIndex : getRunEdge(tempIndex, 1);
3516
3517 runText = model.getText(attributeRunStartIndex,
3518 attributeRunEndIndex -
3519 attributeRunStartIndex);
3520 } catch (BadLocationException e) {
3521 // we are intentionally silent; our contract says we return
3522 // null if there is any failure in this method
3523 return null;
3524 } finally {
3525 if (model instanceof AbstractDocument) {
3526 ((AbstractDocument)model).readUnlock();
3527 }
3528 }
3529 return new AccessibleTextSequence(attributeRunStartIndex,
3530 attributeRunEndIndex,
3531 runText);
3532
3533 default:
3534 break;
3535 }
3536 return null;
3537 }
3538
3539
3540 /**
3541 * Starting at text position <code>index</code>, and going in
3542 * <code>direction</code>, return the edge of run that shares the
3543 * same <code>AttributeSet</code> and parent element as those at
3544 * <code>index</code>.
3545 *
3546 * Note: we assume the document is already locked...
3547 */
3548 private int getRunEdge(int index, int direction) throws
3549 BadLocationException {
3550 if (index < 0 || index >= model.getLength()) {
3551 throw new BadLocationException("Location out of bounds", index);
3552 }
3553 // locate the Element at index
3554 Element indexElement;
3555 // locate the Element at our index/offset
3556 int elementIndex = -1; // test for initialization
3557 for (indexElement = model.getDefaultRootElement();
3558 ! indexElement.isLeaf(); ) {
3559 elementIndex = indexElement.getElementIndex(index);
3560 indexElement = indexElement.getElement(elementIndex);
3561 }
3562 if (elementIndex == -1) {
3563 throw new AssertionError(index);
3564 }
3565 // cache the AttributeSet and parentElement atindex
3566 AttributeSet indexAS = indexElement.getAttributes();
3567 Element parent = indexElement.getParentElement();
3568
3569 // find the first Element before/after ours w/the same AttributeSet
3570 // if we are already at edge of the first element in our parent
3571 // then return that edge
3572 Element edgeElement;
3573 switch (direction) {
3574 case -1:
3575 case 1:
3576 int edgeElementIndex = elementIndex;
3577 int elementCount = parent.getElementCount();
3578 while ((edgeElementIndex + direction) > 0 &&
3579 ((edgeElementIndex + direction) < elementCount) &&
3580 parent.getElement(edgeElementIndex
3581 + direction).getAttributes().isEqual(indexAS)) {
3582 edgeElementIndex += direction;
3583 }
3584 edgeElement = parent.getElement(edgeElementIndex);
3585 break;
3586 default:
3587 throw new AssertionError(direction);
3588 }
3589 switch (direction) {
3590 case -1:
3591 return edgeElement.getStartOffset();
3592 case 1:
3593 return edgeElement.getEndOffset();
3594 default:
3595 // we already caught this case earlier; this is to satisfy
3596 // the compiler...
3597 return Integer.MIN_VALUE;
3598 }
3599 }
3600
3601 // getTextRange() not needed; defined in AccessibleEditableText
3602
3603 /**
3604 * Returns the <code>AccessibleTextSequence</code> at a given
3605 * <code>index</code>.
3606 *
3607 * @param part the <code>CHARACTER</code>, <code>WORD</code>,
3608 * <code>SENTENCE</code>, <code>LINE</code> or
3609 * <code>ATTRIBUTE_RUN</code> to retrieve
3610 * @param index an index within the text
3611 * @return an <code>AccessibleTextSequence</code> specifying the text if
3612 * <code>part</code> and <code>index</code> are valid. Otherwise,
3613 * <code>null</code> is returned
3614 *
3615 * @see javax.accessibility.AccessibleText#CHARACTER
3616 * @see javax.accessibility.AccessibleText#WORD
3617 * @see javax.accessibility.AccessibleText#SENTENCE
3618 * @see javax.accessibility.AccessibleExtendedText#LINE
3619 * @see javax.accessibility.AccessibleExtendedText#ATTRIBUTE_RUN
3620 *
3621 * @since 1.6
3622 */
3623 public AccessibleTextSequence getTextSequenceAt(int part, int index) {
3624 return getSequenceAtIndex(part, index, 0);
3625 }
3626
3627 /**
3628 * Returns the <code>AccessibleTextSequence</code> after a given
3629 * <code>index</code>.
3630 *
3631 * @param part the <code>CHARACTER</code>, <code>WORD</code>,
3632 * <code>SENTENCE</code>, <code>LINE</code> or
3633 * <code>ATTRIBUTE_RUN</code> to retrieve
3634 * @param index an index within the text
3635 * @return an <code>AccessibleTextSequence</code> specifying the text
3636 * if <code>part</code> and <code>index</code> are valid. Otherwise,
3637 * <code>null</code> is returned
3638 *
3639 * @see javax.accessibility.AccessibleText#CHARACTER
3640 * @see javax.accessibility.AccessibleText#WORD
3641 * @see javax.accessibility.AccessibleText#SENTENCE
3642 * @see javax.accessibility.AccessibleExtendedText#LINE
3643 * @see javax.accessibility.AccessibleExtendedText#ATTRIBUTE_RUN
3644 *
3645 * @since 1.6
3646 */
3647 public AccessibleTextSequence getTextSequenceAfter(int part, int index) {
3648 return getSequenceAtIndex(part, index, 1);
3649 }
3650
3651 /**
3652 * Returns the <code>AccessibleTextSequence</code> before a given
3653 * <code>index</code>.
3654 *
3655 * @param part the <code>CHARACTER</code>, <code>WORD</code>,
3656 * <code>SENTENCE</code>, <code>LINE</code> or
3657 * <code>ATTRIBUTE_RUN</code> to retrieve
3658 * @param index an index within the text
3659 * @return an <code>AccessibleTextSequence</code> specifying the text
3660 * if <code>part</code> and <code>index</code> are valid. Otherwise,
3661 * <code>null</code> is returned
3662 *
3663 * @see javax.accessibility.AccessibleText#CHARACTER
3664 * @see javax.accessibility.AccessibleText#WORD
3665 * @see javax.accessibility.AccessibleText#SENTENCE
3666 * @see javax.accessibility.AccessibleExtendedText#LINE
3667 * @see javax.accessibility.AccessibleExtendedText#ATTRIBUTE_RUN
3668 *
3669 * @since 1.6
3670 */
3671 public AccessibleTextSequence getTextSequenceBefore(int part, int index) {
3672 return getSequenceAtIndex(part, index, -1);
3673 }
3674
3675 /**
3676 * Returns the <code>Rectangle</code> enclosing the text between
3677 * two indicies.
3678 *
3679 * @param startIndex the start index in the text
3680 * @param endIndex the end index in the text
3681 * @return the bounding rectangle of the text if the indices are valid.
3682 * Otherwise, <code>null</code> is returned
3683 *
3684 * @since 1.6
3685 */
3686 public Rectangle getTextBounds(int startIndex, int endIndex) {
3687 if (startIndex < 0 || startIndex > model.getLength()-1 ||
3688 endIndex < 0 || endIndex > model.getLength()-1 ||
3689 startIndex > endIndex) {
3690 return null;
3691 }
3692 TextUI ui = getUI();
3693 if (ui == null) {
3694 return null;
3695 }
3696 Rectangle rect = null;
3697 Rectangle alloc = getRootEditorRect();
3698 if (alloc == null) {
3699 return null;
3700 }
3701 if (model instanceof AbstractDocument) {
3702 ((AbstractDocument)model).readLock();
3703 }
3704 try {
3705 View rootView = ui.getRootView(JTextComponent.this);
3706 if (rootView != null) {
3707 Shape bounds = rootView.modelToView(startIndex,
3708 Position.Bias.Forward, endIndex,
3709 Position.Bias.Backward, alloc);
3710
3711 rect = (bounds instanceof Rectangle) ?
3712 (Rectangle)bounds : bounds.getBounds();
3713
3714 }
3715 } catch (BadLocationException e) {
3716 } finally {
3717 if (model instanceof AbstractDocument) {
3718 ((AbstractDocument)model).readUnlock();
3719 }
3720 }
3721 return rect;
3722 }
3723
3724 // ----- end AccessibleExtendedText methods
3725
3726
3727 // --- interface AccessibleAction methods ------------------------
3728
3729 public AccessibleAction getAccessibleAction() {
3730 return this;
3731 }
3732
3733 /**
3734 * Returns the number of accessible actions available in this object
3735 * If there are more than one, the first one is considered the
3736 * "default" action of the object.
3737 *
3738 * @return the zero-based number of Actions in this object
3739 * @since 1.4
3740 */
3741 public int getAccessibleActionCount() {
3742 Action [] actions = JTextComponent.this.getActions();
3743 return actions.length;
3744 }
3745
3746 /**
3747 * Returns a description of the specified action of the object.
3748 *
3749 * @param i zero-based index of the actions
3750 * @return a String description of the action
3751 * @see #getAccessibleActionCount
3752 * @since 1.4
3753 */
3754 public String getAccessibleActionDescription(int i) {
3755 Action [] actions = JTextComponent.this.getActions();
3756 if (i < 0 || i >= actions.length) {
3757 return null;
3758 }
3759 return (String)actions[i].getValue(Action.NAME);
3760 }
3761
3762 /**
3763 * Performs the specified Action on the object
3764 *
3765 * @param i zero-based index of actions
3766 * @return true if the action was performed; otherwise false.
3767 * @see #getAccessibleActionCount
3768 * @since 1.4
3769 */
3770 public boolean doAccessibleAction(int i) {
3771 Action [] actions = JTextComponent.this.getActions();
3772 if (i < 0 || i >= actions.length) {
3773 return false;
3774 }
3775 ActionEvent ae =
3776 new ActionEvent(JTextComponent.this,
3777 ActionEvent.ACTION_PERFORMED, null,
3778 EventQueue.getMostRecentEventTime(),
3779 getCurrentEventModifiers());
3780 actions[i].actionPerformed(ae);
3781 return true;
3782 }
3783
3784 // ----- end AccessibleAction methods
3785
3786
3787 }
3788
3789
3790 // --- serialization ---------------------------------------------
3791
3792 private void readObject(ObjectInputStream s)
3793 throws IOException, ClassNotFoundException
3794 {
3795 s.defaultReadObject();
3796 caretEvent = new MutableCaretEvent(this);
3797 addMouseListener(caretEvent);
3798 addFocusListener(caretEvent);
3799 }
3800
3801 // --- member variables ----------------------------------
3802
3803 /**
3804 * The document model.
3805 */
3806 private Document model;
3807
3808 /**
3809 * The caret used to display the insert position
3810 * and navigate throughout the document.
3811 *
3812 * PENDING(prinz)
3813 * This should be serializable, default installed
3814 * by UI.
3815 */
3816 private transient Caret caret;
3817
3818 /**
3819 * Object responsible for restricting the cursor navigation.
3820 */
3821 private NavigationFilter navigationFilter;
3822
3823 /**
3824 * The object responsible for managing highlights.
3825 *
3826 * PENDING(prinz)
3827 * This should be serializable, default installed
3828 * by UI.
3829 */
3830 private transient Highlighter highlighter;
3831
3832 /**
3833 * The current key bindings in effect.
3834 *
3835 * PENDING(prinz)
3836 * This should be serializable, default installed
3837 * by UI.
3838 */
3839 private transient Keymap keymap;
3840
3841 private transient MutableCaretEvent caretEvent;
3842 private Color caretColor;
3843 private Color selectionColor;
3844 private Color selectedTextColor;
3845 private Color disabledTextColor;
3846 private boolean editable;
3847 private Insets margin;
3848 private char focusAccelerator;
3849 private boolean dragEnabled;
3850
3851 /**
3852 * The drop mode for this component.
3853 */
3854 private DropMode dropMode = DropMode.USE_SELECTION;
3855
3856 /**
3857 * The drop location.
3858 */
3859 private transient DropLocation dropLocation;
3860
3861 /**
3862 * Represents a drop location for <code>JTextComponent</code>s.
3863 *
3864 * @see #getDropLocation
3865 * @since 1.6
3866 */
3867 public static final class DropLocation extends TransferHandler.DropLocation {
3868 private final int index;
3869 private final Position.Bias bias;
3870
3871 private DropLocation(Point p, int index, Position.Bias bias) {
3872 super(p);
3873 this.index = index;
3874 this.bias = bias;
3875 }
3876
3877 /**
3878 * Returns the index where dropped data should be inserted into the
3879 * associated component. This index represents a position between
3880 * characters, as would be interpreted by a caret.
3881 *
3882 * @return the drop index
3883 */
3884 public int getIndex() {
3885 return index;
3886 }
3887
3888 /**
3889 * Returns the bias for the drop index.
3890 *
3891 * @return the drop bias
3892 */
3893 public Position.Bias getBias() {
3894 return bias;
3895 }
3896
3897 /**
3898 * Returns a string representation of this drop location.
3899 * This method is intended to be used for debugging purposes,
3900 * and the content and format of the returned string may vary
3901 * between implementations.
3902 *
3903 * @return a string representation of this drop location
3904 */
3905 public String toString() {
3906 return getClass().getName()
3907 + "[dropPoint=" + getDropPoint() + ","
3908 + "index=" + index + ","
3909 + "bias=" + bias + "]";
3910 }
3911 }
3912
3913 /**
3914 * TransferHandler used if one hasn't been supplied by the UI.
3915 */
3916 private static DefaultTransferHandler defaultTransferHandler;
3917
3918 /**
3919 * Maps from class name to Boolean indicating if
3920 * <code>processInputMethodEvent</code> has been overriden.
3921 */
3922 private static Map<String, Boolean> overrideMap;
3923
3924 /**
3925 * Returns a string representation of this <code>JTextComponent</code>.
3926 * This method is intended to be used only for debugging purposes, and the
3927 * content and format of the returned string may vary between
3928 * implementations. The returned string may be empty but may not
3929 * be <code>null</code>.
3930 * <P>
3931 * Overriding <code>paramString</code> to provide information about the
3932 * specific new aspects of the JFC components.
3933 *
3934 * @return a string representation of this <code>JTextComponent</code>
3935 */
3936 protected String paramString() {
3937 String editableString = (editable ?
3938 "true" : "false");
3939 String caretColorString = (caretColor != null ?
3940 caretColor.toString() : "");
3941 String selectionColorString = (selectionColor != null ?
3942 selectionColor.toString() : "");
3943 String selectedTextColorString = (selectedTextColor != null ?
3944 selectedTextColor.toString() : "");
3945 String disabledTextColorString = (disabledTextColor != null ?
3946 disabledTextColor.toString() : "");
3947 String marginString = (margin != null ?
3948 margin.toString() : "");
3949
3950 return super.paramString() +
3951 ",caretColor=" + caretColorString +
3952 ",disabledTextColor=" + disabledTextColorString +
3953 ",editable=" + editableString +
3954 ",margin=" + marginString +
3955 ",selectedTextColor=" + selectedTextColorString +
3956 ",selectionColor=" + selectionColorString;
3957 }
3958
3959
3960 /**
3961 * A Simple TransferHandler that exports the data as a String, and
3962 * imports the data from the String clipboard. This is only used
3963 * if the UI hasn't supplied one, which would only happen if someone
3964 * hasn't subclassed Basic.
3965 */
3966 static class DefaultTransferHandler extends TransferHandler implements
3967 UIResource {
3968 public void exportToClipboard(JComponent comp, Clipboard clipboard,
3969 int action) throws IllegalStateException {
3970 if (comp instanceof JTextComponent) {
3971 JTextComponent text = (JTextComponent)comp;
3972 int p0 = text.getSelectionStart();
3973 int p1 = text.getSelectionEnd();
3974 if (p0 != p1) {
3975 try {
3976 Document doc = text.getDocument();
3977 String srcData = doc.getText(p0, p1 - p0);
3978 StringSelection contents =new StringSelection(srcData);
3979
3980 // this may throw an IllegalStateException,
3981 // but it will be caught and handled in the
3982 // action that invoked this method
3983 clipboard.setContents(contents, null);
3984
3985 if (action == TransferHandler.MOVE) {
3986 doc.remove(p0, p1 - p0);
3987 }
3988 } catch (BadLocationException ble) {}
3989 }
3990 }
3991 }
3992 public boolean importData(JComponent comp, Transferable t) {
3993 if (comp instanceof JTextComponent) {
3994 DataFlavor flavor = getFlavor(t.getTransferDataFlavors());
3995
3996 if (flavor != null) {
3997 InputContext ic = comp.getInputContext();
3998 if (ic != null) {
3999 ic.endComposition();
4000 }
4001 try {
4002 String data = (String)t.getTransferData(flavor);
4003
4004 ((JTextComponent)comp).replaceSelection(data);
4005 return true;
4006 } catch (UnsupportedFlavorException ufe) {
4007 } catch (IOException ioe) {
4008 }
4009 }
4010 }
4011 return false;
4012 }
4013 public boolean canImport(JComponent comp,
4014 DataFlavor[] transferFlavors) {
4015 JTextComponent c = (JTextComponent)comp;
4016 if (!(c.isEditable() && c.isEnabled())) {
4017 return false;
4018 }
4019 return (getFlavor(transferFlavors) != null);
4020 }
4021 public int getSourceActions(JComponent c) {
4022 return NONE;
4023 }
4024 private DataFlavor getFlavor(DataFlavor[] flavors) {
4025 if (flavors != null) {
4026 for (DataFlavor flavor : flavors) {
4027 if (flavor.equals(DataFlavor.stringFlavor)) {
4028 return flavor;
4029 }
4030 }
4031 }
4032 return null;
4033 }
4034 }
4035
4036 /**
4037 * Returns the JTextComponent that most recently had focus. The returned
4038 * value may currently have focus.
4039 */
4040 static final JTextComponent getFocusedComponent() {
4041 return (JTextComponent)AppContext.getAppContext().
4042 get(FOCUSED_COMPONENT);
4043 }
4044
4045 private int getCurrentEventModifiers() {
4046 int modifiers = 0;
4047 AWTEvent currentEvent = EventQueue.getCurrentEvent();
4048 if (currentEvent instanceof InputEvent) {
4049 modifiers = ((InputEvent)currentEvent).getModifiers();
4050 } else if (currentEvent instanceof ActionEvent) {
4051 modifiers = ((ActionEvent)currentEvent).getModifiers();
4052 }
4053 return modifiers;
4054 }
4055
4056 private static final Object KEYMAP_TABLE =
4057 new StringBuilder("JTextComponent_KeymapTable");
4058
4059 //
4060 // member variables used for on-the-spot input method
4061 // editing style support
4062 //
4063 private transient InputMethodRequests inputMethodRequestsHandler;
4064 private SimpleAttributeSet composedTextAttribute;
4065 private String composedTextContent;
4066 private Position composedTextStart;
4067 private Position composedTextEnd;
4068 private Position latestCommittedTextStart;
4069 private Position latestCommittedTextEnd;
4070 private ComposedTextCaret composedTextCaret;
4071 private transient Caret originalCaret;
4072 /**
4073 * Set to true after the check for the override of processInputMethodEvent
4074 * has been checked.
4075 */
4076 private boolean checkedInputOverride;
4077 private boolean needToSendKeyTypedEvent;
4078
4079 static class DefaultKeymap implements Keymap {
4080
4081 DefaultKeymap(String nm, Keymap parent) {
4082 this.nm = nm;
4083 this.parent = parent;
4084 bindings = new Hashtable<KeyStroke, Action>();
4085 }
4086
4087 /**
4088 * Fetch the default action to fire if a
4089 * key is typed (ie a KEY_TYPED KeyEvent is received)
4090 * and there is no binding for it. Typically this
4091 * would be some action that inserts text so that
4092 * the keymap doesn't require an action for each
4093 * possible key.
4094 */
4095 public Action getDefaultAction() {
4096 if (defaultAction != null) {
4097 return defaultAction;
4098 }
4099 return (parent != null) ? parent.getDefaultAction() : null;
4100 }
4101
4102 /**
4103 * Set the default action to fire if a key is typed.
4104 */
4105 public void setDefaultAction(Action a) {
4106 defaultAction = a;
4107 }
4108
4109 public String getName() {
4110 return nm;
4111 }
4112
4113 public Action getAction(KeyStroke key) {
4114 Action a = bindings.get(key);
4115 if ((a == null) && (parent != null)) {
4116 a = parent.getAction(key);
4117 }
4118 return a;
4119 }
4120
4121 public KeyStroke[] getBoundKeyStrokes() {
4122 KeyStroke[] keys = new KeyStroke[bindings.size()];
4123 int i = 0;
4124 for (Enumeration<KeyStroke> e = bindings.keys() ; e.hasMoreElements() ;) {
4125 keys[i++] = e.nextElement();
4126 }
4127 return keys;
4128 }
4129
4130 public Action[] getBoundActions() {
4131 Action[] actions = new Action[bindings.size()];
4132 int i = 0;
4133 for (Enumeration<Action> e = bindings.elements() ; e.hasMoreElements() ;) {
4134 actions[i++] = e.nextElement();
4135 }
4136 return actions;
4137 }
4138
4139 public KeyStroke[] getKeyStrokesForAction(Action a) {
4140 if (a == null) {
4141 return null;
4142 }
4143 KeyStroke[] retValue = null;
4144 // Determine local bindings first.
4145 Vector<KeyStroke> keyStrokes = null;
4146 for (Enumeration<KeyStroke> keys = bindings.keys(); keys.hasMoreElements();) {
4147 KeyStroke key = keys.nextElement();
4148 if (bindings.get(key) == a) {
4149 if (keyStrokes == null) {
4150 keyStrokes = new Vector<KeyStroke>();
4151 }
4152 keyStrokes.addElement(key);
4153 }
4154 }
4155 // See if the parent has any.
4156 if (parent != null) {
4157 KeyStroke[] pStrokes = parent.getKeyStrokesForAction(a);
4158 if (pStrokes != null) {
4159 // Remove any bindings defined in the parent that
4160 // are locally defined.
4161 int rCount = 0;
4162 for (int counter = pStrokes.length - 1; counter >= 0;
4163 counter--) {
4164 if (isLocallyDefined(pStrokes[counter])) {
4165 pStrokes[counter] = null;
4166 rCount++;
4167 }
4168 }
4169 if (rCount > 0 && rCount < pStrokes.length) {
4170 if (keyStrokes == null) {
4171 keyStrokes = new Vector<KeyStroke>();
4172 }
4173 for (int counter = pStrokes.length - 1; counter >= 0;
4174 counter--) {
4175 if (pStrokes[counter] != null) {
4176 keyStrokes.addElement(pStrokes[counter]);
4177 }
4178 }
4179 }
4180 else if (rCount == 0) {
4181 if (keyStrokes == null) {
4182 retValue = pStrokes;
4183 }
4184 else {
4185 retValue = new KeyStroke[keyStrokes.size() +
4186 pStrokes.length];
4187 keyStrokes.copyInto(retValue);
4188 System.arraycopy(pStrokes, 0, retValue,
4189 keyStrokes.size(), pStrokes.length);
4190 keyStrokes = null;
4191 }
4192 }
4193 }
4194 }
4195 if (keyStrokes != null) {
4196 retValue = new KeyStroke[keyStrokes.size()];
4197 keyStrokes.copyInto(retValue);
4198 }
4199 return retValue;
4200 }
4201
4202 public boolean isLocallyDefined(KeyStroke key) {
4203 return bindings.containsKey(key);
4204 }
4205
4206 public void addActionForKeyStroke(KeyStroke key, Action a) {
4207 bindings.put(key, a);
4208 }
4209
4210 public void removeKeyStrokeBinding(KeyStroke key) {
4211 bindings.remove(key);
4212 }
4213
4214 public void removeBindings() {
4215 bindings.clear();
4216 }
4217
4218 public Keymap getResolveParent() {
4219 return parent;
4220 }
4221
4222 public void setResolveParent(Keymap parent) {
4223 this.parent = parent;
4224 }
4225
4226 /**
4227 * String representation of the keymap... potentially
4228 * a very long string.
4229 */
4230 public String toString() {
4231 return "Keymap[" + nm + "]" + bindings;
4232 }
4233
4234 String nm;
4235 Keymap parent;
4236 Hashtable<KeyStroke, Action> bindings;
4237 Action defaultAction;
4238 }
4239
4240
4241 /**
4242 * KeymapWrapper wraps a Keymap inside an InputMap. For KeymapWrapper
4243 * to be useful it must be used with a KeymapActionMap.
4244 * KeymapWrapper for the most part, is an InputMap with two parents.
4245 * The first parent visited is ALWAYS the Keymap, with the second
4246 * parent being the parent inherited from InputMap. If
4247 * <code>keymap.getAction</code> returns null, implying the Keymap
4248 * does not have a binding for the KeyStroke,
4249 * the parent is then visited. If the Keymap has a binding, the
4250 * Action is returned, if not and the KeyStroke represents a
4251 * KeyTyped event and the Keymap has a defaultAction,
4252 * <code>DefaultActionKey</code> is returned.
4253 * <p>KeymapActionMap is then able to transate the object passed in
4254 * to either message the Keymap, or message its default implementation.
4255 */
4256 static class KeymapWrapper extends InputMap {
4257 static final Object DefaultActionKey = new Object();
4258
4259 private Keymap keymap;
4260
4261 KeymapWrapper(Keymap keymap) {
4262 this.keymap = keymap;
4263 }
4264
4265 public KeyStroke[] keys() {
4266 KeyStroke[] sKeys = super.keys();
4267 KeyStroke[] keymapKeys = keymap.getBoundKeyStrokes();
4268 int sCount = (sKeys == null) ? 0 : sKeys.length;
4269 int keymapCount = (keymapKeys == null) ? 0 : keymapKeys.length;
4270 if (sCount == 0) {
4271 return keymapKeys;
4272 }
4273 if (keymapCount == 0) {
4274 return sKeys;
4275 }
4276 KeyStroke[] retValue = new KeyStroke[sCount + keymapCount];
4277 // There may be some duplication here...
4278 System.arraycopy(sKeys, 0, retValue, 0, sCount);
4279 System.arraycopy(keymapKeys, 0, retValue, sCount, keymapCount);
4280 return retValue;
4281 }
4282
4283 public int size() {
4284 // There may be some duplication here...
4285 KeyStroke[] keymapStrokes = keymap.getBoundKeyStrokes();
4286 int keymapCount = (keymapStrokes == null) ? 0:
4287 keymapStrokes.length;
4288 return super.size() + keymapCount;
4289 }
4290
4291 public Object get(KeyStroke keyStroke) {
4292 Object retValue = keymap.getAction(keyStroke);
4293 if (retValue == null) {
4294 retValue = super.get(keyStroke);
4295 if (retValue == null &&
4296 keyStroke.getKeyChar() != KeyEvent.CHAR_UNDEFINED &&
4297 keymap.getDefaultAction() != null) {
4298 // Implies this is a KeyTyped event, use the default
4299 // action.
4300 retValue = DefaultActionKey;
4301 }
4302 }
4303 return retValue;
4304 }
4305 }
4306
4307
4308 /**
4309 * Wraps a Keymap inside an ActionMap. This is used with
4310 * a KeymapWrapper. If <code>get</code> is passed in
4311 * <code>KeymapWrapper.DefaultActionKey</code>, the default action is
4312 * returned, otherwise if the key is an Action, it is returned.
4313 */
4314 static class KeymapActionMap extends ActionMap {
4315 private Keymap keymap;
4316
4317 KeymapActionMap(Keymap keymap) {
4318 this.keymap = keymap;
4319 }
4320
4321 public Object[] keys() {
4322 Object[] sKeys = super.keys();
4323 Object[] keymapKeys = keymap.getBoundActions();
4324 int sCount = (sKeys == null) ? 0 : sKeys.length;
4325 int keymapCount = (keymapKeys == null) ? 0 : keymapKeys.length;
4326 boolean hasDefault = (keymap.getDefaultAction() != null);
4327 if (hasDefault) {
4328 keymapCount++;
4329 }
4330 if (sCount == 0) {
4331 if (hasDefault) {
4332 Object[] retValue = new Object[keymapCount];
4333 if (keymapCount > 1) {
4334 System.arraycopy(keymapKeys, 0, retValue, 0,
4335 keymapCount - 1);
4336 }
4337 retValue[keymapCount - 1] = KeymapWrapper.DefaultActionKey;
4338 return retValue;
4339 }
4340 return keymapKeys;
4341 }
4342 if (keymapCount == 0) {
4343 return sKeys;
4344 }
4345 Object[] retValue = new Object[sCount + keymapCount];
4346 // There may be some duplication here...
4347 System.arraycopy(sKeys, 0, retValue, 0, sCount);
4348 if (hasDefault) {
4349 if (keymapCount > 1) {
4350 System.arraycopy(keymapKeys, 0, retValue, sCount,
4351 keymapCount - 1);
4352 }
4353 retValue[sCount + keymapCount - 1] = KeymapWrapper.
4354 DefaultActionKey;
4355 }
4356 else {
4357 System.arraycopy(keymapKeys, 0, retValue, sCount, keymapCount);
4358 }
4359 return retValue;
4360 }
4361
4362 public int size() {
4363 // There may be some duplication here...
4364 Object[] actions = keymap.getBoundActions();
4365 int keymapCount = (actions == null) ? 0 : actions.length;
4366 if (keymap.getDefaultAction() != null) {
4367 keymapCount++;
4368 }
4369 return super.size() + keymapCount;
4370 }
4371
4372 public Action get(Object key) {
4373 Action retValue = super.get(key);
4374 if (retValue == null) {
4375 // Try the Keymap.
4376 if (key == KeymapWrapper.DefaultActionKey) {
4377 retValue = keymap.getDefaultAction();
4378 }
4379 else if (key instanceof Action) {
4380 // This is a little iffy, technically an Action is
4381 // a valid Key. We're assuming the Action came from
4382 // the InputMap though.
4383 retValue = (Action)key;
4384 }
4385 }
4386 return retValue;
4387 }
4388 }
4389
4390 private static final Object FOCUSED_COMPONENT =
4391 new StringBuilder("JTextComponent_FocusedComponent");
4392
4393 /**
4394 * The default keymap that will be shared by all
4395 * <code>JTextComponent</code> instances unless they
4396 * have had a different keymap set.
4397 */
4398 public static final String DEFAULT_KEYMAP = "default";
4399
4400 /**
4401 * Event to use when firing a notification of change to caret
4402 * position. This is mutable so that the event can be reused
4403 * since caret events can be fairly high in bandwidth.
4404 */
4405 static class MutableCaretEvent extends CaretEvent implements ChangeListener, FocusListener, MouseListener {
4406
4407 MutableCaretEvent(JTextComponent c) {
4408 super(c);
4409 }
4410
4411 final void fire() {
4412 JTextComponent c = (JTextComponent) getSource();
4413 if (c != null) {
4414 Caret caret = c.getCaret();
4415 dot = caret.getDot();
4416 mark = caret.getMark();
4417 c.fireCaretUpdate(this);
4418 }
4419 }
4420
4421 public final String toString() {
4422 return "dot=" + dot + "," + "mark=" + mark;
4423 }
4424
4425 // --- CaretEvent methods -----------------------
4426
4427 public final int getDot() {
4428 return dot;
4429 }
4430
4431 public final int getMark() {
4432 return mark;
4433 }
4434
4435 // --- ChangeListener methods -------------------
4436
4437 public final void stateChanged(ChangeEvent e) {
4438 if (! dragActive) {
4439 fire();
4440 }
4441 }
4442
4443 // --- FocusListener methods -----------------------------------
4444 public void focusGained(FocusEvent fe) {
4445 AppContext.getAppContext().put(FOCUSED_COMPONENT,
4446 fe.getSource());
4447 }
4448
4449 public void focusLost(FocusEvent fe) {
4450 }
4451
4452 // --- MouseListener methods -----------------------------------
4453
4454 /**
4455 * Requests focus on the associated
4456 * text component, and try to set the cursor position.
4457 *
4458 * @param e the mouse event
4459 * @see MouseListener#mousePressed
4460 */
4461 public final void mousePressed(MouseEvent e) {
4462 dragActive = true;
4463 }
4464
4465 /**
4466 * Called when the mouse is released.
4467 *
4468 * @param e the mouse event
4469 * @see MouseListener#mouseReleased
4470 */
4471 public final void mouseReleased(MouseEvent e) {
4472 dragActive = false;
4473 fire();
4474 }
4475
4476 public final void mouseClicked(MouseEvent e) {
4477 }
4478
4479 public final void mouseEntered(MouseEvent e) {
4480 }
4481
4482 public final void mouseExited(MouseEvent e) {
4483 }
4484
4485 private boolean dragActive;
4486 private int dot;
4487 private int mark;
4488 }
4489
4490 //
4491 // Process any input method events that the component itself
4492 // recognizes. The default on-the-spot handling for input method
4493 // composed(uncommitted) text is done here after all input
4494 // method listeners get called for stealing the events.
4495 //
4496 protected void processInputMethodEvent(InputMethodEvent e) {
4497 // let listeners handle the events
4498 super.processInputMethodEvent(e);
4499
4500 if (!e.isConsumed()) {
4501 if (! isEditable()) {
4502 return;
4503 } else {
4504 switch (e.getID()) {
4505 case InputMethodEvent.INPUT_METHOD_TEXT_CHANGED:
4506 replaceInputMethodText(e);
4507
4508 // fall through
4509
4510 case InputMethodEvent.CARET_POSITION_CHANGED:
4511 setInputMethodCaretPosition(e);
4512 break;
4513 }
4514 }
4515
4516 e.consume();
4517 }
4518 }
4519
4520 //
4521 // Overrides this method to become an active input method client.
4522 //
4523 public InputMethodRequests getInputMethodRequests() {
4524 if (inputMethodRequestsHandler == null) {
4525 inputMethodRequestsHandler = new InputMethodRequestsHandler();
4526 Document doc = getDocument();
4527 if (doc != null) {
4528 doc.addDocumentListener((DocumentListener)inputMethodRequestsHandler);
4529 }
4530 }
4531
4532 return inputMethodRequestsHandler;
4533 }
4534
4535 //
4536 // Overrides this method to watch the listener installed.
4537 //
4538 public void addInputMethodListener(InputMethodListener l) {
4539 super.addInputMethodListener(l);
4540 if (l != null) {
4541 needToSendKeyTypedEvent = false;
4542 checkedInputOverride = true;
4543 }
4544 }
4545
4546
4547 //
4548 // Default implementation of the InputMethodRequests interface.
4549 //
4550 class InputMethodRequestsHandler implements InputMethodRequests, DocumentListener {
4551
4552 // --- InputMethodRequests methods ---
4553
4554 public AttributedCharacterIterator cancelLatestCommittedText(
4555 Attribute[] attributes) {
4556 Document doc = getDocument();
4557 if ((doc != null) && (latestCommittedTextStart != null)
4558 && (!latestCommittedTextStart.equals(latestCommittedTextEnd))) {
4559 try {
4560 int startIndex = latestCommittedTextStart.getOffset();
4561 int endIndex = latestCommittedTextEnd.getOffset();
4562 String latestCommittedText =
4563 doc.getText(startIndex, endIndex - startIndex);
4564 doc.remove(startIndex, endIndex - startIndex);
4565 return new AttributedString(latestCommittedText).getIterator();
4566 } catch (BadLocationException ble) {}
4567 }
4568 return null;
4569 }
4570
4571 public AttributedCharacterIterator getCommittedText(int beginIndex,
4572 int endIndex, Attribute[] attributes) {
4573 int composedStartIndex = 0;
4574 int composedEndIndex = 0;
4575 if (composedTextExists()) {
4576 composedStartIndex = composedTextStart.getOffset();
4577 composedEndIndex = composedTextEnd.getOffset();
4578 }
4579
4580 String committed;
4581 try {
4582 if (beginIndex < composedStartIndex) {
4583 if (endIndex <= composedStartIndex) {
4584 committed = getText(beginIndex, endIndex - beginIndex);
4585 } else {
4586 int firstPartLength = composedStartIndex - beginIndex;
4587 committed = getText(beginIndex, firstPartLength) +
4588 getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
4589 }
4590 } else {
4591 committed = getText(beginIndex + (composedEndIndex - composedStartIndex),
4592 endIndex - beginIndex);
4593 }
4594 } catch (BadLocationException ble) {
4595 throw new IllegalArgumentException("Invalid range");
4596 }
4597 return new AttributedString(committed).getIterator();
4598 }
4599
4600 public int getCommittedTextLength() {
4601 Document doc = getDocument();
4602 int length = 0;
4603 if (doc != null) {
4604 length = doc.getLength();
4605 if (composedTextContent != null) {
4606 if (composedTextEnd == null
4607 || composedTextStart == null) {
4608 /*
4609 * fix for : 6355666
4610 * this is the case when this method is invoked
4611 * from DocumentListener. At this point
4612 * composedTextEnd and composedTextStart are
4613 * not defined yet.
4614 */
4615 length -= composedTextContent.length();
4616 } else {
4617 length -= composedTextEnd.getOffset() -
4618 composedTextStart.getOffset();
4619 }
4620 }
4621 }
4622 return length;
4623 }
4624
4625 public int getInsertPositionOffset() {
4626 int composedStartIndex = 0;
4627 int composedEndIndex = 0;
4628 if (composedTextExists()) {
4629 composedStartIndex = composedTextStart.getOffset();
4630 composedEndIndex = composedTextEnd.getOffset();
4631 }
4632 int caretIndex = getCaretPosition();
4633
4634 if (caretIndex < composedStartIndex) {
4635 return caretIndex;
4636 } else if (caretIndex < composedEndIndex) {
4637 return composedStartIndex;
4638 } else {
4639 return caretIndex - (composedEndIndex - composedStartIndex);
4640 }
4641 }
4642
4643 public TextHitInfo getLocationOffset(int x, int y) {
4644 if (composedTextAttribute == null) {
4645 return null;
4646 } else {
4647 Point p = getLocationOnScreen();
4648 p.x = x - p.x;
4649 p.y = y - p.y;
4650 int pos = viewToModel(p);
4651 if ((pos >= composedTextStart.getOffset()) &&
4652 (pos <= composedTextEnd.getOffset())) {
4653 return TextHitInfo.leading(pos - composedTextStart.getOffset());
4654 } else {
4655 return null;
4656 }
4657 }
4658 }
4659
4660 public Rectangle getTextLocation(TextHitInfo offset) {
4661 Rectangle r;
4662
4663 try {
4664 r = modelToView(getCaretPosition());
4665 if (r != null) {
4666 Point p = getLocationOnScreen();
4667 r.translate(p.x, p.y);
4668 }
4669 } catch (BadLocationException ble) {
4670 r = null;
4671 }
4672
4673 if (r == null)
4674 r = new Rectangle();
4675
4676 return r;
4677 }
4678
4679 public AttributedCharacterIterator getSelectedText(
4680 Attribute[] attributes) {
4681 String selection = JTextComponent.this.getSelectedText();
4682 if (selection != null) {
4683 return new AttributedString(selection).getIterator();
4684 } else {
4685 return null;
4686 }
4687 }
4688
4689 // --- DocumentListener methods ---
4690
4691 public void changedUpdate(DocumentEvent e) {
4692 latestCommittedTextStart = latestCommittedTextEnd = null;
4693 }
4694
4695 public void insertUpdate(DocumentEvent e) {
4696 latestCommittedTextStart = latestCommittedTextEnd = null;
4697 }
4698
4699 public void removeUpdate(DocumentEvent e) {
4700 latestCommittedTextStart = latestCommittedTextEnd = null;
4701 }
4702 }
4703
4704 //
4705 // Replaces the current input method (composed) text according to
4706 // the passed input method event. This method also inserts the
4707 // committed text into the document.
4708 //
4709 private void replaceInputMethodText(InputMethodEvent e) {
4710 int commitCount = e.getCommittedCharacterCount();
4711 AttributedCharacterIterator text = e.getText();
4712 int composedTextIndex;
4713
4714 // old composed text deletion
4715 Document doc = getDocument();
4716 if (composedTextExists()) {
4717 try {
4718 doc.remove(composedTextStart.getOffset(),
4719 composedTextEnd.getOffset() -
4720 composedTextStart.getOffset());
4721 } catch (BadLocationException ble) {}
4722 composedTextStart = composedTextEnd = null;
4723 composedTextAttribute = null;
4724 composedTextContent = null;
4725 }
4726
4727 if (text != null) {
4728 text.first();
4729 int committedTextStartIndex = 0;
4730 int committedTextEndIndex = 0;
4731
4732 // committed text insertion
4733 if (commitCount > 0) {
4734 // Remember latest committed text start index
4735 committedTextStartIndex = caret.getDot();
4736
4737 // Need to generate KeyTyped events for the committed text for components
4738 // that are not aware they are active input method clients.
4739 if (shouldSynthensizeKeyEvents()) {
4740 for (char c = text.current(); commitCount > 0;
4741 c = text.next(), commitCount--) {
4742 KeyEvent ke = new KeyEvent(this, KeyEvent.KEY_TYPED,
4743 EventQueue.getMostRecentEventTime(),
4744 0, KeyEvent.VK_UNDEFINED, c);
4745 processKeyEvent(ke);
4746 }
4747 } else {
4748 StringBuilder strBuf = new StringBuilder();
4749 for (char c = text.current(); commitCount > 0;
4750 c = text.next(), commitCount--) {
4751 strBuf.append(c);
4752 }
4753
4754 // map it to an ActionEvent
4755 mapCommittedTextToAction(strBuf.toString());
4756 }
4757
4758 // Remember latest committed text end index
4759 committedTextEndIndex = caret.getDot();
4760 }
4761
4762 // new composed text insertion
4763 composedTextIndex = text.getIndex();
4764 if (composedTextIndex < text.getEndIndex()) {
4765 createComposedTextAttribute(composedTextIndex, text);
4766 try {
4767 replaceSelection(null);
4768 doc.insertString(caret.getDot(), composedTextContent,
4769 composedTextAttribute);
4770 composedTextStart = doc.createPosition(caret.getDot() -
4771 composedTextContent.length());
4772 composedTextEnd = doc.createPosition(caret.getDot());
4773 } catch (BadLocationException ble) {
4774 composedTextStart = composedTextEnd = null;
4775 composedTextAttribute = null;
4776 composedTextContent = null;
4777 }
4778 }
4779
4780 // Save the latest committed text information
4781 if (committedTextStartIndex != committedTextEndIndex) {
4782 try {
4783 latestCommittedTextStart = doc.
4784 createPosition(committedTextStartIndex);
4785 latestCommittedTextEnd = doc.
4786 createPosition(committedTextEndIndex);
4787 } catch (BadLocationException ble) {
4788 latestCommittedTextStart =
4789 latestCommittedTextEnd = null;
4790 }
4791 } else {
4792 latestCommittedTextStart =
4793 latestCommittedTextEnd = null;
4794 }
4795 }
4796 }
4797
4798 private void createComposedTextAttribute(int composedIndex,
4799 AttributedCharacterIterator text) {
4800 Document doc = getDocument();
4801 StringBuilder strBuf = new StringBuilder();
4802
4803 // create attributed string with no attributes
4804 for (char c = text.setIndex(composedIndex);
4805 c != CharacterIterator.DONE; c = text.next()) {
4806 strBuf.append(c);
4807 }
4808
4809 composedTextContent = strBuf.toString();
4810 composedTextAttribute = new SimpleAttributeSet();
4811 composedTextAttribute.addAttribute(StyleConstants.ComposedTextAttribute,
4812 new AttributedString(text, composedIndex, text.getEndIndex()));
4813 }
4814
4815 /**
4816 * Saves composed text around the specified position.
4817 *
4818 * The composed text (if any) around the specified position is saved
4819 * in a backing store and removed from the document.
4820 *
4821 * @param pos document position to identify the composed text location
4822 * @return {@code true} if the composed text exists and is saved,
4823 * {@code false} otherwise
4824 * @see #restoreComposedText
4825 * @since 1.7
4826 */
4827 protected boolean saveComposedText(int pos) {
4828 if (composedTextExists()) {
4829 int start = composedTextStart.getOffset();
4830 int len = composedTextEnd.getOffset() -
4831 composedTextStart.getOffset();
4832 if (pos >= start && pos <= start + len) {
4833 try {
4834 getDocument().remove(start, len);
4835 return true;
4836 } catch (BadLocationException ble) {}
4837 }
4838 }
4839 return false;
4840 }
4841
4842 /**
4843 * Restores composed text previously saved by {@code saveComposedText}.
4844 *
4845 * The saved composed text is inserted back into the document. This method
4846 * should be invoked only if {@code saveComposedText} returns {@code true}.
4847 *
4848 * @see #saveComposedText
4849 * @since 1.7
4850 */
4851 protected void restoreComposedText() {
4852 Document doc = getDocument();
4853 try {
4854 doc.insertString(caret.getDot(),
4855 composedTextContent,
4856 composedTextAttribute);
4857 composedTextStart = doc.createPosition(caret.getDot() -
4858 composedTextContent.length());
4859 composedTextEnd = doc.createPosition(caret.getDot());
4860 } catch (BadLocationException ble) {}
4861 }
4862
4863 //
4864 // Map committed text to an ActionEvent. If the committed text length is 1,
4865 // treat it as a KeyStroke, otherwise or there is no KeyStroke defined,
4866 // treat it just as a default action.
4867 //
4868 private void mapCommittedTextToAction(String committedText) {
4869 Keymap binding = getKeymap();
4870 if (binding != null) {
4871 Action a = null;
4872 if (committedText.length() == 1) {
4873 KeyStroke k = KeyStroke.getKeyStroke(committedText.charAt(0));
4874 a = binding.getAction(k);
4875 }
4876
4877 if (a == null) {
4878 a = binding.getDefaultAction();
4879 }
4880
4881 if (a != null) {
4882 ActionEvent ae =
4883 new ActionEvent(this, ActionEvent.ACTION_PERFORMED,
4884 committedText,
4885 EventQueue.getMostRecentEventTime(),
4886 getCurrentEventModifiers());
4887 a.actionPerformed(ae);
4888 }
4889 }
4890 }
4891
4892 //
4893 // Sets the caret position according to the passed input method
4894 // event. Also, sets/resets composed text caret appropriately.
4895 //
4896 private void setInputMethodCaretPosition(InputMethodEvent e) {
4897 int dot;
4898
4899 if (composedTextExists()) {
4900 dot = composedTextStart.getOffset();
4901 if (!(caret instanceof ComposedTextCaret)) {
4902 if (composedTextCaret == null) {
4903 composedTextCaret = new ComposedTextCaret();
4904 }
4905 originalCaret = caret;
4906 // Sets composed text caret
4907 exchangeCaret(originalCaret, composedTextCaret);
4908 }
4909
4910 TextHitInfo caretPos = e.getCaret();
4911 if (caretPos != null) {
4912 int index = caretPos.getInsertionIndex();
4913 dot += index;
4914 if (index == 0) {
4915 // Scroll the component if needed so that the composed text
4916 // becomes visible.
4917 try {
4918 Rectangle d = modelToView(dot);
4919 Rectangle end = modelToView(composedTextEnd.getOffset());
4920 Rectangle b = getBounds();
4921 d.x += Math.min(end.x - d.x, b.width);
4922 scrollRectToVisible(d);
4923 } catch (BadLocationException ble) {}
4924 }
4925 }
4926 caret.setDot(dot);
4927 } else if (caret instanceof ComposedTextCaret) {
4928 dot = caret.getDot();
4929 // Restores original caret
4930 exchangeCaret(caret, originalCaret);
4931 caret.setDot(dot);
4932 }
4933 }
4934
4935 private void exchangeCaret(Caret oldCaret, Caret newCaret) {
4936 int blinkRate = oldCaret.getBlinkRate();
4937 setCaret(newCaret);
4938 caret.setBlinkRate(blinkRate);
4939 caret.setVisible(hasFocus());
4940 }
4941
4942 /**
4943 * Returns true if KeyEvents should be synthesized from an InputEvent.
4944 */
4945 private boolean shouldSynthensizeKeyEvents() {
4946 if (!checkedInputOverride) {
4947 checkedInputOverride = true;
4948 needToSendKeyTypedEvent =
4949 !isProcessInputMethodEventOverridden();
4950 }
4951 return needToSendKeyTypedEvent;
4952 }
4953
4954 //
4955 // Checks whether the client code overrides processInputMethodEvent. If it is overridden,
4956 // need not to generate KeyTyped events for committed text. If it's not, behave as an
4957 // passive input method client.
4958 //
4959 private boolean isProcessInputMethodEventOverridden() {
4960 if (overrideMap == null) {
4961 overrideMap = Collections.synchronizedMap(new HashMap<String, Boolean>());
4962 }
4963 Boolean retValue = overrideMap.get(getClass().getName());
4964
4965 if (retValue != null) {
4966 return retValue.booleanValue();
4967 }
4968 Boolean ret = AccessController.doPrivileged(new
4969 PrivilegedAction<Boolean>() {
4970 public Boolean run() {
4971 return isProcessInputMethodEventOverridden(
4972 JTextComponent.this.getClass());
4973 }
4974 });
4975
4976 return ret.booleanValue();
4977 }
4978
4979 //
4980 // Checks whether a composed text in this text component
4981 //
4982 boolean composedTextExists() {
4983 return (composedTextStart != null);
4984 }
4985
4986 //
4987 // Caret implementation for editing the composed text.
4988 //
4989 class ComposedTextCaret extends DefaultCaret implements Serializable {
4990 Color bg;
4991
4992 //
4993 // Get the background color of the component
4994 //
4995 public void install(JTextComponent c) {
4996 super.install(c);
4997
4998 Document doc = c.getDocument();
4999 if (doc instanceof StyledDocument) {
5000 StyledDocument sDoc = (StyledDocument)doc;
5001 Element elem = sDoc.getCharacterElement(c.composedTextStart.getOffset());
5002 AttributeSet attr = elem.getAttributes();
5003 bg = sDoc.getBackground(attr);
5004 }
5005
5006 if (bg == null) {
5007 bg = c.getBackground();
5008 }
5009 }
5010
5011 //
5012 // Draw caret in XOR mode.
5013 //
5014 public void paint(Graphics g) {
5015 if(isVisible()) {
5016 try {
5017 Rectangle r = component.modelToView(getDot());
5018 g.setXORMode(bg);
5019 g.drawLine(r.x, r.y, r.x, r.y + r.height - 1);
5020 g.setPaintMode();
5021 } catch (BadLocationException e) {
5022 // can't render I guess
5023 //System.err.println("Can't render cursor");
5024 }
5025 }
5026 }
5027
5028 //
5029 // If some area other than the composed text is clicked by mouse,
5030 // issue endComposition() to force commit the composed text.
5031 //
5032 protected void positionCaret(MouseEvent me) {
5033 JTextComponent host = component;
5034 Point pt = new Point(me.getX(), me.getY());
5035 int offset = host.viewToModel(pt);
5036 int composedStartIndex = host.composedTextStart.getOffset();
5037 if ((offset < composedStartIndex) ||
5038 (offset > composedTextEnd.getOffset())) {
5039 try {
5040 // Issue endComposition
5041 Position newPos = host.getDocument().createPosition(offset);
5042 host.getInputContext().endComposition();
5043
5044 // Post a caret positioning runnable to assure that the positioning
5045 // occurs *after* committing the composed text.
5046 EventQueue.invokeLater(new DoSetCaretPosition(host, newPos));
5047 } catch (BadLocationException ble) {
5048 System.err.println(ble);
5049 }
5050 } else {
5051 // Normal processing
5052 super.positionCaret(me);
5053 }
5054 }
5055 }
5056
5057 //
5058 // Runnable class for invokeLater() to set caret position later.
5059 //
5060 private class DoSetCaretPosition implements Runnable {
5061 JTextComponent host;
5062 Position newPos;
5063
5064 DoSetCaretPosition(JTextComponent host, Position newPos) {
5065 this.host = host;
5066 this.newPos = newPos;
5067 }
5068
5069 public void run() {
5070 host.setCaretPosition(newPos.getOffset());
5071 }
5072 }
5073 }